From b7cdf59341eae1f03466c34097e8c7da236401ff Mon Sep 17 00:00:00 2001
From: lucascouts <lucascs@protonmail.com>
Date: Wed, 8 Jul 2026 18:50:49 -0300
Subject: [PATCH] feat(agent_ui): collapsible markdown previews for elicitation
 options

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 crates/agent_ui/src/conversation_view.rs      |  32 +-
 .../src/conversation_view/elicitation.rs      | 412 +++++++++++++++++-
 .../src/conversation_view/thread_view.rs      |  16 +-
 3 files changed, 445 insertions(+), 15 deletions(-)

diff --git a/crates/agent_ui/src/conversation_view.rs b/crates/agent_ui/src/conversation_view.rs
index 110961b..8ab4a60 100644
--- a/crates/agent_ui/src/conversation_view.rs
+++ b/crates/agent_ui/src/conversation_view.rs
@@ -2491,13 +2491,33 @@ impl ConversationView {
                     .log_err();
                 }
             },
-            move |elicitation_id, field_name, value, selected, cx| {
+            {
+                let view = view.clone();
+                move |elicitation_id, field_name, value, selected, cx| {
+                    view.update(cx, |this, cx| {
+                        this.update_request_elicitation_form_state(
+                            &elicitation_id,
+                            |form| form.set_multi_select(&field_name, value, selected),
+                            cx,
+                        );
+                    })
+                    .log_err();
+                }
+            },
+            move |elicitation_id, field_name, value, preview_text, cx| {
                 view.update(cx, |this, cx| {
-                    this.update_request_elicitation_form_state(
-                        &elicitation_id,
-                        |form| form.set_multi_select(&field_name, value, selected),
-                        cx,
-                    );
+                    let toggled = if let Some(form) = this
+                        .request_elicitation_form_states
+                        .get_mut(&elicitation_id)
+                    {
+                        form.toggle_preview(&field_name, &value, &preview_text, cx);
+                        true
+                    } else {
+                        false
+                    };
+                    if toggled {
+                        this.notify_request_elicitation_renderers(cx);
+                    }
                 })
                 .log_err();
             },
diff --git a/crates/agent_ui/src/conversation_view/elicitation.rs b/crates/agent_ui/src/conversation_view/elicitation.rs
index 55bb938..e225a86 100644
--- a/crates/agent_ui/src/conversation_view/elicitation.rs
+++ b/crates/agent_ui/src/conversation_view/elicitation.rs
@@ -4,12 +4,18 @@ use collections::{HashMap, HashSet};
 use component::{Component, ComponentScope, example_group_with_title, single_example};
 use editor::Editor;
 use futures::channel::oneshot;
-use gpui::{AnyElement, App, Div, Empty, Entity, Hsla, SharedString, Window, div};
+use gpui::{
+    AnyElement, App, Div, Empty, Entity, Hsla, Refineable, SharedString, TextStyle,
+    TextStyleRefinement, Window, div,
+};
+use markdown::{Markdown, MarkdownElement, MarkdownStyle};
+use settings::Settings as _;
 use std::collections::BTreeMap;
 use std::rc::Rc;
+use theme_settings::ThemeSettings;
 use ui::{
-    Button, Checkbox, Color, Icon, IconName, IconSize, Indicator, Label, LabelSize, ToggleState,
-    prelude::*,
+    Button, Checkbox, Color, Disclosure, Icon, IconName, IconSize, Indicator, Label, LabelSize,
+    TextSize, ToggleState, prelude::*,
 };
 
 #[derive(Clone)]
@@ -17,6 +23,7 @@ struct ElicitationOption {
     value: String,
     label: SharedString,
     description: Option<SharedString>,
+    preview: Option<String>,
 }
 
 enum ElicitationFieldState {
@@ -29,6 +36,8 @@ enum ElicitationFieldState {
 pub(crate) struct ElicitationFormState {
     fields: HashMap<String, ElicitationFieldState>,
     field_errors: HashMap<String, SharedString>,
+    expanded_previews: HashSet<(String, String)>,
+    preview_markdown: HashMap<(String, String), Entity<Markdown>>,
 }
 
 impl ElicitationFormState {
@@ -100,6 +109,8 @@ impl ElicitationFormState {
         Self {
             fields,
             field_errors: HashMap::default(),
+            expanded_previews: HashSet::default(),
+            preview_markdown: HashMap::default(),
         }
     }
 
@@ -285,6 +296,41 @@ impl ElicitationFormState {
             self.field_errors.remove(field_name);
         }
     }
+
+    /// Toggle the expanded state of an option's preview. The `Markdown` entity is
+    /// created lazily on first expansion and reused thereafter (never rebuilt per
+    /// render). Collapsing keeps the entity cached for a cheap re-expand.
+    pub(crate) fn toggle_preview(
+        &mut self,
+        field_name: &str,
+        option_value: &str,
+        preview_text: &str,
+        cx: &mut App,
+    ) {
+        let key = (field_name.to_string(), option_value.to_string());
+        if !self.expanded_previews.remove(&key) {
+            if !self.preview_markdown.contains_key(&key) {
+                let source = preview_markdown_source(preview_text);
+                let markdown = cx.new(|cx| Markdown::new(source.into(), None, None, cx));
+                self.preview_markdown.insert(key.clone(), markdown);
+            }
+            self.expanded_previews.insert(key);
+        }
+    }
+
+    fn is_preview_expanded(&self, field_name: &str, option_value: &str) -> bool {
+        self.expanded_previews
+            .contains(&(field_name.to_string(), option_value.to_string()))
+    }
+
+    fn preview_markdown_entity(
+        &self,
+        field_name: &str,
+        option_value: &str,
+    ) -> Option<&Entity<Markdown>> {
+        self.preview_markdown
+            .get(&(field_name.to_string(), option_value.to_string()))
+    }
 }
 
 #[cfg(test)]
@@ -669,6 +715,224 @@ mod tests {
             Editor::single_line(window, cx)
         });
     }
+
+    // ---- helpers (skip any that already exist in the target `mod tests`) ----
+
+    fn option_meta_entry(entry: serde_json::Value) -> acp::Meta {
+        let mut meta = serde_json::Map::new();
+        meta.insert(ASK_USER_QUESTION_OPTION_META_KEY.to_string(), entry);
+        meta
+    }
+
+    fn titled_option_with_meta(
+        value: &str,
+        title: &str,
+        entry: serde_json::Value,
+    ) -> acp::EnumOption {
+        let mut option = acp::EnumOption::new(value, title);
+        option.meta = Some(option_meta_entry(entry));
+        option
+    }
+
+    // ---- R1.1: preview extraction from per-option `_meta` ----
+
+    #[test]
+    fn option_preview_parses_from_meta() {
+        let meta = option_meta_entry(serde_json::json!({
+            "preview": "```rust\nfn main() {}\n```",
+        }));
+        assert_eq!(
+            ask_user_question_option_preview(Some(&meta)).as_deref(),
+            Some("```rust\nfn main() {}\n```")
+        );
+    }
+
+    #[test]
+    fn option_preview_tolerates_unknown_extra_fields() {
+        let meta = option_meta_entry(serde_json::json!({
+            "preview": "kept",
+            "futureField": { "nested": true },
+        }));
+        assert_eq!(
+            ask_user_question_option_preview(Some(&meta)).as_deref(),
+            Some("kept")
+        );
+    }
+
+    // ---- R1.2: no preview -> no preview surface (reader yields None, never errors) ----
+
+    #[test]
+    fn option_preview_absent_when_meta_absent() {
+        assert!(ask_user_question_option_preview(None).is_none());
+    }
+
+    #[test]
+    fn option_preview_absent_when_key_absent() {
+        let mut meta = serde_json::Map::new();
+        meta.insert(
+            "_other/extension".to_string(),
+            serde_json::json!({ "preview": "ignored" }),
+        );
+        assert!(ask_user_question_option_preview(Some(&meta)).is_none());
+    }
+
+    #[test]
+    fn option_preview_ignores_non_object_entry() {
+        let meta = option_meta_entry(serde_json::json!("not-an-object"));
+        assert!(ask_user_question_option_preview(Some(&meta)).is_none());
+    }
+
+    #[test]
+    fn option_preview_ignores_wrong_field_type() {
+        let meta = option_meta_entry(serde_json::json!({ "preview": ["not", "a", "string"] }));
+        assert!(ask_user_question_option_preview(Some(&meta)).is_none());
+    }
+
+    // ---- R1.3: reader is preview-only; native description path untouched ----
+
+    #[test]
+    fn option_preview_ignores_description_only_meta() {
+        let meta = option_meta_entry(serde_json::json!({ "description": "The live environment" }));
+        assert!(
+            ask_user_question_option_preview(Some(&meta)).is_none(),
+            "the 0006 reader is reduced to preview; description stays on the pin's native path"
+        );
+    }
+
+    // ---- R1.1/R1.2: preview threads into option models ----
+
+    #[test]
+    fn single_select_threads_option_preview() {
+        let schema = acp::StringPropertySchema::new().one_of(vec![titled_option_with_meta(
+            "production",
+            "Production",
+            serde_json::json!({ "preview": "# Production" }),
+        )]);
+        let options = single_select_options(&schema);
+        assert_eq!(options.len(), 1);
+        assert_eq!(options[0].value, "production");
+        assert_eq!(options[0].preview.as_deref(), Some("# Production"));
+    }
+
+    #[test]
+    fn enum_values_options_carry_no_preview() {
+        let schema = acp::StringPropertySchema::new()
+            .enum_values(vec!["alpha".to_string(), "beta".to_string()]);
+        let options = single_select_options(&schema);
+        assert!(options.iter().all(|option| option.preview.is_none()));
+    }
+
+    #[test]
+    fn multi_select_titled_threads_option_preview() {
+        let schema = acp::MultiSelectPropertySchema::titled(vec![titled_option_with_meta(
+            "repository",
+            "Repository",
+            serde_json::json!({ "preview": "```\ndiff\n```" }),
+        )]);
+        let options = multi_select_options(&schema);
+        assert_eq!(options[0].value, "repository");
+        assert_eq!(options[0].preview.as_deref(), Some("```\ndiff\n```"));
+    }
+
+    #[test]
+    fn multi_select_untitled_options_carry_no_preview() {
+        let schema =
+            acp::MultiSelectPropertySchema::new(vec!["alpha".to_string(), "beta".to_string()]);
+        let options = multi_select_options(&schema);
+        assert!(options.iter().all(|option| option.preview.is_none()));
+    }
+
+    // ---- preview markdown source: never render raw HTML ----
+
+    #[test]
+    fn preview_markdown_source_fences_html_like_content() {
+        assert_eq!(
+            preview_markdown_source("<div>hi</div>"),
+            "```\n<div>hi</div>\n```"
+        );
+        assert_eq!(
+            preview_markdown_source("  \n  <span>x</span>"),
+            "```\n  \n  <span>x</span>\n```"
+        );
+    }
+
+    #[test]
+    fn preview_markdown_source_leaves_markdown_untouched() {
+        assert_eq!(preview_markdown_source("# Heading"), "# Heading");
+        assert_eq!(
+            preview_markdown_source("```rust\nfn main() {}\n```"),
+            "```rust\nfn main() {}\n```"
+        );
+    }
+
+    // ---- R1.1: render presence — expansion state creates and reuses the Markdown entity ----
+
+    #[gpui::test]
+    fn toggle_preview_round_trips_and_reuses_entity(cx: &mut TestAppContext) {
+        crate::conversation_view::tests::init_test(cx);
+
+        cx.add_window(|window, cx| {
+            let schema = acp::ElicitationSchema::new().property(
+                "environment",
+                acp::StringPropertySchema::new()
+                    .title("Environment")
+                    .one_of(vec![titled_option_with_meta(
+                        "production",
+                        "Production",
+                        serde_json::json!({ "preview": "# Production" }),
+                    )]),
+                false,
+            );
+            let mut form_state = ElicitationFormState::new(&schema, window, cx);
+            assert!(!form_state.is_preview_expanded("environment", "production"));
+
+            form_state.toggle_preview("environment", "production", "# Production", cx);
+            assert!(form_state.is_preview_expanded("environment", "production"));
+            let first = form_state
+                .preview_markdown_entity("environment", "production")
+                .expect("entity created on first expansion")
+                .entity_id();
+
+            form_state.toggle_preview("environment", "production", "# Production", cx);
+            assert!(!form_state.is_preview_expanded("environment", "production"));
+
+            form_state.toggle_preview("environment", "production", "# Production", cx);
+            let second = form_state
+                .preview_markdown_entity("environment", "production")
+                .expect("entity retained across toggles")
+                .entity_id();
+            assert_eq!(first, second, "markdown entity is reused, not recreated");
+
+            Editor::single_line(window, cx)
+        });
+    }
+
+    // ---- R1.2: options without preview never grow preview state ----
+
+    #[gpui::test]
+    fn no_preview_option_creates_no_preview_state(cx: &mut TestAppContext) {
+        crate::conversation_view::tests::init_test(cx);
+
+        cx.add_window(|window, cx| {
+            let schema = acp::ElicitationSchema::new().property(
+                "environment",
+                acp::StringPropertySchema::new()
+                    .title("Environment")
+                    .one_of(vec![acp::EnumOption::new("staging", "Staging")]),
+                false,
+            );
+            let form_state = ElicitationFormState::new(&schema, window, cx);
+            assert!(!form_state.is_preview_expanded("environment", "staging"));
+            assert!(
+                form_state
+                    .preview_markdown_entity("environment", "staging")
+                    .is_none(),
+                "no preview content means no preview surface (form renders as the unpatched pin)"
+            );
+
+            Editor::single_line(window, cx)
+        });
+    }
 }
 
 #[derive(RegisterComponent)]
@@ -883,6 +1147,54 @@ fn preview_form_schema() -> acp::ElicitationSchema {
         )
 }
 
+/// Key under which the Claude Code adapter attaches per-option preview
+/// metadata to an elicitation enum option's `_meta` (adapter >= v0.46.0).
+const ASK_USER_QUESTION_OPTION_META_KEY: &str = "_claude/askUserQuestionOption";
+
+/// Extract the optional `preview` string from an enum option's `_meta` under
+/// [`ASK_USER_QUESTION_OPTION_META_KEY`], tolerantly. An absent key, a non-object
+/// entry, or a wrong-typed field all yield `None` — never an error (R1.2) — and
+/// other fields are ignored: option descriptions render via the native
+/// `EnumOption::description` path at this pin (R1.3).
+fn ask_user_question_option_preview(meta: Option<&acp::Meta>) -> Option<String> {
+    meta.and_then(|meta| meta.get(ASK_USER_QUESTION_OPTION_META_KEY))
+        .and_then(|value| value.as_object())
+        .and_then(|entry| entry.get("preview"))
+        .and_then(|value| value.as_str())
+        .map(|preview| preview.to_string())
+}
+
+/// Wrap an HTML-looking preview in a fenced code block so Markdown renders it as
+/// escaped plain text — the card never renders raw HTML.
+fn preview_markdown_source(preview: &str) -> String {
+    if preview.trim_start().starts_with('<') {
+        format!("```\n{preview}\n```")
+    } else {
+        preview.to_string()
+    }
+}
+
+/// Minimal muted Markdown style for inline option previews, built from theme
+/// settings (the render path has no `Window`, so `text_style()` is unavailable).
+fn preview_markdown_style(cx: &App) -> MarkdownStyle {
+    let colors = cx.theme().colors();
+    let theme_settings = ThemeSettings::get_global(cx);
+    let mut base_text_style = TextStyle::default();
+    base_text_style.refine(&TextStyleRefinement {
+        font_family: Some(theme_settings.ui_font.family.clone()),
+        font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
+        font_features: Some(theme_settings.ui_font.features.clone()),
+        font_size: Some(TextSize::XSmall.rems(cx).into()),
+        color: Some(colors.text_muted),
+        ..Default::default()
+    });
+    MarkdownStyle {
+        base_text_style,
+        selection_background_color: colors.element_selection_background,
+        ..Default::default()
+    }
+}
+
 fn single_select_options(schema: &acp::StringPropertySchema) -> Vec<ElicitationOption> {
     if let Some(options) = &schema.one_of {
         return options
@@ -891,6 +1203,7 @@ fn single_select_options(schema: &acp::StringPropertySchema) -> Vec<ElicitationO
                 value: option.value.clone(),
                 label: SharedString::from(option.title.clone()),
                 description: option.description.clone().map(SharedString::from),
+                preview: ask_user_question_option_preview(option.meta.as_ref()),
             })
             .collect();
     }
@@ -904,6 +1217,7 @@ fn single_select_options(schema: &acp::StringPropertySchema) -> Vec<ElicitationO
             value: value.clone(),
             label: SharedString::from(value.clone()),
             description: None,
+            preview: None,
         })
         .collect()
 }
@@ -932,6 +1246,7 @@ fn multi_select_options(schema: &acp::MultiSelectPropertySchema) -> Vec<Elicitat
                 value: value.clone(),
                 label: SharedString::from(value.clone()),
                 description: None,
+                preview: None,
             })
             .collect(),
         acp::MultiSelectItems::Titled(items) => items
@@ -941,6 +1256,7 @@ fn multi_select_options(schema: &acp::MultiSelectPropertySchema) -> Vec<Elicitat
                 value: option.value.clone(),
                 label: SharedString::from(option.title.clone()),
                 description: option.description.clone().map(SharedString::from),
+                preview: ask_user_question_option_preview(option.meta.as_ref()),
             })
             .collect(),
         _ => Vec::new(),
@@ -1131,6 +1447,7 @@ type OpenUrlHandler = Rc<dyn Fn(ElicitationEntryId, String, &mut Window, &mut Ap
 type BooleanHandler = Rc<dyn Fn(ElicitationEntryId, String, bool, &mut App)>;
 type SelectHandler = Rc<dyn Fn(ElicitationEntryId, String, String, &mut App)>;
 type MultiSelectHandler = Rc<dyn Fn(ElicitationEntryId, String, String, bool, &mut App)>;
+type TogglePreviewHandler = Rc<dyn Fn(ElicitationEntryId, String, String, String, &mut App)>;
 
 #[derive(Clone)]
 pub(crate) struct ElicitationCardHandlers {
@@ -1141,6 +1458,7 @@ pub(crate) struct ElicitationCardHandlers {
     on_boolean_change: BooleanHandler,
     on_single_select_change: SelectHandler,
     on_multi_select_change: MultiSelectHandler,
+    on_toggle_preview: TogglePreviewHandler,
 }
 
 impl ElicitationCardHandlers {
@@ -1152,6 +1470,7 @@ impl ElicitationCardHandlers {
         on_boolean_change: impl Fn(ElicitationEntryId, String, bool, &mut App) + 'static,
         on_single_select_change: impl Fn(ElicitationEntryId, String, String, &mut App) + 'static,
         on_multi_select_change: impl Fn(ElicitationEntryId, String, String, bool, &mut App) + 'static,
+        on_toggle_preview: impl Fn(ElicitationEntryId, String, String, String, &mut App) + 'static,
     ) -> Self {
         Self {
             on_submit: Rc::new(on_submit),
@@ -1161,6 +1480,7 @@ impl ElicitationCardHandlers {
             on_boolean_change: Rc::new(on_boolean_change),
             on_single_select_change: Rc::new(on_single_select_change),
             on_multi_select_change: Rc::new(on_multi_select_change),
+            on_toggle_preview: Rc::new(on_toggle_preview),
         }
     }
 
@@ -1173,6 +1493,7 @@ impl ElicitationCardHandlers {
             |_, _, _, _| {},
             |_, _, _, _| {},
             |_, _, _, _, _| {},
+            |_, _, _, _, _| {},
         )
     }
 }
@@ -1481,11 +1802,14 @@ impl<'a> ElicitationCard<'a> {
                             let elicitation_id = self.elicitation.id.clone();
                             let field_name = field_name.to_string();
                             let value = option.value.clone();
+                            let preview = option.preview.clone();
+                            let preview_field = field_name.clone();
+                            let preview_value = option.value.clone();
                             let checkbox_id = format!(
                                 "elicitation-multi-{}-{field_name}-{}",
                                 self.entry_ix, option.value
                             );
-                            h_flex()
+                            let row = h_flex()
                                 .id(SharedString::from(format!(
                                     "elicitation-multi-option-{}-{field_name}-{}",
                                     self.entry_ix, option.value
@@ -1511,7 +1835,18 @@ impl<'a> ElicitationCard<'a> {
                                     );
                                 })
                                 .child(div().child(Checkbox::new(checkbox_id, checkbox_state)))
-                                .child(Self::render_option_content(option))
+                                .child(Self::render_option_content(option));
+                            v_flex()
+                                .gap_1()
+                                .child(row)
+                                .when_some(preview, |this, preview| {
+                                    this.child(self.render_option_preview(
+                                        &preview_field,
+                                        &preview_value,
+                                        &preview,
+                                        cx,
+                                    ))
+                                })
                         }))
                         .into_any_element()
                 }
@@ -1551,11 +1886,14 @@ impl<'a> ElicitationCard<'a> {
                 let row_background = Self::option_row_background(is_selected, cx);
                 let hover_background = Self::option_row_hover_background(is_selected, cx);
                 let control_background = Self::option_control_background(cx);
+                let preview = option.preview.clone();
+                let preview_field = field_name.clone();
+                let preview_value = option.value.clone();
                 let elicitation_id = elicitation_id.clone();
                 let field_name = field_name.clone();
                 let on_single_select_change = on_single_select_change.clone();
 
-                h_flex()
+                let row = h_flex()
                     .id(option_id)
                     .w_full()
                     .min_h(rems_from_px(28.))
@@ -1589,11 +1927,71 @@ impl<'a> ElicitationCard<'a> {
                                 control_background,
                             )),
                     )
-                    .child(Self::render_option_content(option))
+                    .child(Self::render_option_content(option));
+                v_flex()
+                    .gap_1()
+                    .child(row)
+                    .when_some(preview, |this, preview| {
+                        this.child(self.render_option_preview(
+                            &preview_field,
+                            &preview_value,
+                            &preview,
+                            cx,
+                        ))
+                    })
             }))
             .into_any_element()
     }
 
+    /// Render the collapsible preview surface for an option that carries one.
+    /// The disclosure row is always present for such options; the Markdown body
+    /// renders only while expanded (entity cached in [`ElicitationFormState`]).
+    fn render_option_preview(
+        &self,
+        field_name: &str,
+        option_value: &str,
+        preview: &str,
+        cx: &App,
+    ) -> AnyElement {
+        let Some(state) = self.form_state else {
+            return Empty.into_any_element();
+        };
+        let is_open = state.is_preview_expanded(field_name, option_value);
+        let markdown = state
+            .preview_markdown_entity(field_name, option_value)
+            .cloned();
+        let disclosure_id = SharedString::from(format!(
+            "elicitation-preview-{}-{field_name}-{option_value}",
+            self.entry_ix
+        ));
+        let on_toggle_preview = self.handlers.on_toggle_preview.clone();
+        let elicitation_id = self.elicitation.id.clone();
+        let field_name = field_name.to_string();
+        let option_value = option_value.to_string();
+        let preview = preview.to_string();
+
+        v_flex()
+            .pl_5()
+            .gap_1()
+            .child(
+                Disclosure::new(disclosure_id, is_open).on_click(move |_, _window, cx| {
+                    on_toggle_preview(
+                        elicitation_id.clone(),
+                        field_name.clone(),
+                        option_value.clone(),
+                        preview.clone(),
+                        cx,
+                    );
+                }),
+            )
+            .when(is_open, |this| {
+                this.when_some(markdown, |this, markdown| {
+                    this.child(MarkdownElement::new(markdown, preview_markdown_style(cx)))
+                })
+            })
+            .into_any_element()
+    }
+
     fn render_option_content(option: ElicitationOption) -> Div {
         v_flex()
             .min_w_0()
diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs
index fe8b7b2..7db6acd 100644
--- a/crates/agent_ui/src/conversation_view/thread_view.rs
+++ b/crates/agent_ui/src/conversation_view/thread_view.rs
@@ -6551,10 +6551,22 @@ impl ThreadView {
                     .log_err();
                 }
             },
-            move |elicitation_id, field_name, value, selected, cx| {
+            {
+                let view = view.clone();
+                move |elicitation_id, field_name, value, selected, cx| {
+                    view.update(cx, |this, cx| {
+                        if let Some(form) = this.elicitation_form_states.get_mut(&elicitation_id) {
+                            form.set_multi_select(&field_name, value, selected);
+                            cx.notify();
+                        }
+                    })
+                    .log_err();
+                }
+            },
+            move |elicitation_id, field_name, value, preview_text, cx| {
                 view.update(cx, |this, cx| {
                     if let Some(form) = this.elicitation_form_states.get_mut(&elicitation_id) {
-                        form.set_multi_select(&field_name, value, selected);
+                        form.toggle_preview(&field_name, &value, &preview_text, cx);
                         cx.notify();
                     }
                 })
-- 
2.55.0

