                     Changes in version 0.5.0.9000                      

New features and improvements

  - Text file attachments are now sent to the model via
    ellmer::content_document_url() rather than a hand-assembled
    <file-attachment> text wrapper, so providers that handle documents
    natively receive a real document. This raises the minimum required
    ellmer version to 0.5.0. (#415)

  - Streamed responses now reveal smoothly at a typing-like pace instead
    of in abrupt server-sized chunks, with far fewer re-renders along
    the way. Bursts of text catch up quickly, the reveal never flashes a
    partial HTML tag, and stopping a response reveals any buffered text
    immediately. The trailing dot now appears only once a stream has
    stalled (or before any text has arrived), rather than hopping along
    with every update. (#411)

Bug fixes

  - Expanding a running tool call's row in a tool group now shows its
    arguments. Previously the panel stayed empty until the call
    finished. (#424)

  - Stopping an in-progress response now returns focus to the chat
    input, even inside a Shiny modal. During streaming, Escape dismisses
    an open slash-command menu or history drawer first; a subsequent
    Escape cancels the response without closing the modal. (thanks
    @taekop, #416)

  - Fixed conversation history being silently lost on redeploy on Posit
    Connect when CONNECT_CONTENT_DATA_DIR is unavailable; it now
    correctly persists to Connect's bookmark storage instead.

                 Changes in version 0.5.0 (2026-09-09)                  

API additions

  - Added chat_server() as the new primary way to wire up server-side
    chat logic. Pair it with chat_ui() by matching id, e.g.
    chat_ui("chat") with chat_server("chat", client). It does the same
    job as chat_mod_server() but runs directly in the caller's session
    scope rather than creating its own module scope, and it enables many
    of the features under New features and improvements below
    automatically. chat_mod_server() and chat_mod_ui() are now
    soft-deprecated in favor of chat_server() and chat_ui(). (#264)

  - Added page_chat() for full-window chat pages with persistent chat
    navigation, responsive sidebars, and drawers. Register secondary
    pages with chat_nav_panel(), configure sidebars with chat_sidebar(),
    and add a drawer with chat_drawer(), controllable from the server
    via chat_drawer_show(), chat_drawer_update(), chat_drawer_hide(),
    and chat_drawer_toggle(). Related additions: chat_ui_history() for
    mounting conversation history outside the chat, a page_chat_theme()
    baseline theme, and drawer/show_history options on chat_ui(). (#329)
    
      - The page shell also supports standard bslib programmatic
        navigation (e.g. bslib::nav_select()), with the active page
        readable server-side as input$<id>_page.

  - Added slash commands: a typeahead command palette that lets users
    trigger named shortcuts directly from the chat input (type / to open
    it). Commands can expand into LLM prompts, trigger server-side side
    effects (clear chat, open a modal, export transcript), or be handled
    entirely client-side via the cancelable shiny:chat-slash-command DOM
    event. Register commands with the slash_command() method of the
    object returned by chat_server(); its echo parameter controls
    whether an invocation is recorded as a user message and triggers a
    loading state. (#239)

  - Added chat_get_greeting() for reading the current greeting (and
    whether the user dismissed it) from the server, along with a new
    input$<id>_greeting_dismissed event. Server-set greetings now also
    survive Shiny bookmarking round-trips. (#254)

New features and improvements

  - chat_server() gets multi-conversation history automatically: a
    drawer for starting new chats and returning to previous ones, with
    LLM-generated titles, search, rename, and delete. Conversations are
    persisted per-user (or a custom scope) via a pluggable store (the
    default FileConversationStore works out of the box, including on
    Posit Connect). Customize with history = history_options(...) — e.g.
    how the active conversation is restored across reloads (browser
    storage, URL, or Shiny bookmarking) and callbacks to keep app state
    synced to it — or opt out with history = FALSE. The history object
    also offers programmatic control, including a reactive conversation
    ID and an explicit save() method. For apps that can't use
    chat_server(), wire it up manually with chat_enable_history().
    (#266, #307, #328)

  - You can now edit and resend a message after sending it. Editing
    forks the conversation from that point — the original branch is kept
    as a sibling, and ‹ 1 / 2 › controls let you switch between versions
    at any time, including after reloading the page or returning from
    the history drawer. Requires history to be enabled (the default with
    chat_server()). (#269)

  - Cancelling an in-progress response is now automatic with
    chat_server(): the stop button appears by default and cancellation
    is handled for you. (#264)

  - Added file attachment support: users can upload images, PDFs, and
    text files alongside their messages. chat_server() enables
    attachments by default and automatically converts uploads into
    ellmer Content objects for the model. For non-chat_server() usage,
    enable with allow_attachments = TRUE (or a MIME allow-list). The
    maximum combined attachment size defaults to approximately 30 MB and
    can be configured via the SHINYCHAT_MAX_ATTACHMENT_SIZE environment
    variable. (#250)
    
      - When attachments are enabled, input$<id>_user_input is a list of
        ellmer Content objects (typed text, if present, followed by one
        object per attachment) rather than a plain string — forward it
        to a chat method by splicing with !!!, e.g.
        chat$stream_async(!!!input$<id>_user_input). The last_input
        reactive returned by chat_server() mirrors this shape.

  - Web search and web fetch responses from ellmer now show their
    activity and citations directly in the chat. Readers can open a
    citation beside its claim or use the message-wide Sources pill.
    ContentCitation@grounded_span links each citation to the answer text
    it supports. (#280)
    
      - Citations are powered by a new <shiny-aside> markup convention
        that any assistant message can use to attach source details to
        specific claims — useful for custom RAG workflows. See the
        Asides section in ?chat_append. (#278)

  - Tool call displays have been reworked to be more concise and to
    intelligently group multiple calls together. By default, calls
    render as a condensed activity row; expand a group row to see each
    call, then drill into a call for its full request/result card.
    (#283)
    
      - Added tool_result_display(), a constructor for the display
        object passed as extra = list(display = ...) on an
        ellmer::ContentToolResult (a bare named list with the same
        fields still works). New label and value_preview fields (e.g. a
        filename, and "1,204 rows") are shown in the activity row.
      - A tool's definition title (from its annotations) and its result
        title (from tool_result_display()) are now shown as-is, without
        client-side tense conjugation — the old "Running {title}" /
        "{title} failed" templates are gone. The definition title shows
        while the call is running; for a single-call row, the result
        title (if provided) replaces it when the result arrives, and
        failures are shown via a separate status cue. If a title now
        reads oddly while running, write it in the present tense (e.g.
        "Running R code").
      - Control grouping with the tool_grouping parameter of chat_ui():
        "tool" (default) groups calls to the same tool within a
        tool-calling loop, "all" groups every call in the loop together,
        and "none" shows one activity row per call (thinking or prose
        starts a new loop). Individual tools can override the chat-level
        setting with a grouping tool annotation, e.g. tool(...,
        annotations = tool_annotations(grouping = "all")).
      - Set open_style = "framed" in tool_result_display() to draw a
        border around an open tool result's header and contents — a
        better fit for results with a footer or fullscreen toggle.
        (#331)
      - Fully custom tool-result UI returned from a contents_shinychat()
        method is now paired with its tool request: while the tool runs
        it appears in the activity row, and once the result arrives the
        custom UI renders as standalone output. Custom results are also
        preserved when preloading or restoring conversations.

  - Added submit_key parameter to chat_ui(): "enter" (default, Enter
    submits) or "enter+modifier" (Ctrl/Cmd+Enter submits, plain Enter
    inserts a line break). The input remains editable while a response
    is streaming — only submission is blocked, not typing. (#251)

  - The send button is easier to customize: a new icon_send parameter on
    chat_ui() swaps the submit icon, and a data-state attribute exposes
    the button's current state for styling. (#350)

  - Styling is more customizable via CSS custom properties: the set of
    public --shiny-chat-* variables has grown to cover the send button,
    history drawer, page layout, thinking display, and more, and all of
    them can now be overridden from :root (previously, element-level
    defaults always won). (#350, #355)

  - Single tildes no longer trigger strikethrough in markdown. Text like
    (~$1.50) and ~/Documents now renders as literal text; only ~~text~~
    produces strikethrough. (#349, #353)

Breaking changes

  - chat_app() now configures its full-window page through page_chat().
    Arguments in ... are passed to page_chat() instead of
    shiny::shinyApp(): use app_options instead of options,
    bookmark_store instead of enableBookmarking, and compose page_chat()
    with chat_server() when you need onStart or uiPattern. chat_app()
    now owns the page layout, so its title, icon, and id configure the
    page-chat shell. Existing layouts that embed chat should use
    chat_ui() and chat_server() directly. (#329)

  - The messages parameter is deprecated in favor of the
    conversation-history feature: chat_ui(messages = ...) now warns, and
    chat_app(messages = ...) errors when history is enabled (the
    default). Use greeting for a startup message, chat_append() to
    replay messages, or history = FALSE if you manage state yourself.
    (#381)

  - CSS classes and custom properties used by the external-link dialog,
    thinking display, and tool-result images/PDFs now use the
    .shiny-chat-* prefix instead of .shinychat-*. Update any custom CSS
    that targets these identifiers. (#285, #286)

Changes

  - chat_ui() and page_chat() no longer show an assistant icon by
    default. Pass icon_assistant = TRUE to restore the built-in robot
    icon, or supply your own icon as before. (#345)

  - chat_ui() now uses a wider default content width on large displays
    while preserving its existing width on smaller windows. (#364)

  - The dismissible parameter of chat_greeting() has been renamed to
    persistent with an inverted value. dismissible = FALSE (greeting
    stays visible) is now persistent = TRUE. The old dismissible
    argument still works but warns. (#260)

Bug fixes

  - Fixed a security issue where model-authored Markdown could create or
    escape into shinychat's raw-HTML islands (<shiny-chat-raw-html>),
    potentially injecting arbitrary HTML into the page. As part of this,
    output_markdown_stream()/markdown_stream() now track which parts of
    a mixed value are trusted server-rendered HTML versus untrusted
    text. (#287, #360)

  - A response that fails before it streams anything (e.g. an exhausted
    quota, an over-long context, or a dropped connection) is now
    reported in the chat instead of leaving a loading indicator that
    never resolves and a locked composer. The chat_server() return also
    gained last_error, a reactive holding the condition from the most
    recent failed response, since both a finished and a failed response
    report "idle" in status. (#304, #314)

  - Fixed output_markdown_stream() permanently stopping following new
    content after the user scrolled back to the bottom. (#282)

  - Fixed expanding or collapsing a tool result yanking the chat's
    scroll position away from what you were reading. (#348)

  - chat_app() no longer renders a close button or registers a stopApp()
    observer when deployed to a server. Both are now gated on
    rlang::is_interactive(), preventing session crashes in multi-user
    deployments. (#265)

  - Fixed control-only inputs (the close button, cancel button, and
    greeting-requested signal) leaking into bookmarked state. (#258,
    #259)

  - Fixed set_client() failing when the conversation contains an
    htmlwidget or tagList(). (#362, #369)

  - Fixed suggestion cards and the greeting overflowing the chat
    container in narrow spaces such as sidebars. (#255)

  - Fixed the copy button on code blocks not working in some embedded
    contexts. (@thisisnic, #247)

                 Changes in version 0.4.0 (2026-06-01)                  

Experimental internal changes

  - The chat UI's rendering layer has been migrated from Lit to React.
    This significantly improves streaming performance — incoming chunks
    no longer clear previous DOM state — and makes the codebase more
    maintainable. One trade-off is that certain Shiny UI elements
    embedded in chat messages may not work as well as before (e.g.,
    inline <script> tags are generally not supported inside a React
    runtime). If you encounter issues, please let us know.

New features and improvements

  - The chat UI now displays model reasoning/thinking content as
    collapsible panels above assistant responses. Thinking content
    streams in real-time with animated topic labels. This works with
    providers that support structured thinking (e.g., Claude's extended
    thinking via ellmer) and with local models that wrap reasoning in
    <thinking> tags. (#208)

  - Added enable_cancel parameter to chat_ui() to show a stop button
    that lets users cancel an in-progress AI response. Press the stop
    button or hit Escape to cancel. chat_mod_ui() enables cancellation
    by default, and chat_mod_server() handles the cancellation wiring
    automatically, using the stream cancellation features introduced in
    ellmer v0.4.1. (#221)

  - Markdown lists where every item is a <span class="suggestion"> are
    now rendered as a grid of clickable suggestion cards. Each
    suggestion's text content becomes both the card label and the value
    sent on click. To add a short heading above the body text, set the
    title attribute on the span — e.g. <span class="suggestion"
    title="Heading">Body text shown on the card.</span>. Only the body
    text (not the title) is submitted when the card is clicked. Cards
    stream in with staggered animations and support keyboard navigation
    (arrow keys, Home/End) with roving tabindex. (#219)

  - Added chat_greeting() for creating welcome messages that appear when
    the chat is empty. Greetings can be set statically via
    chat_ui(greeting=) or dynamically from the server with
    chat_set_greeting(). They are automatically dismissed when the user
    sends their first message. A new greeting_requested input fires when
    the chat is visible, empty, and has no greeting, enabling
    LLM-generated welcome messages. chat_mod_server(greeting=) accepts a
    function for auto-generated greetings. (#217)

  - Tool result cards now render images and PDFs returned by ellmer
    tools. When a tool returns content_image_file(),
    content_image_url(), or content_pdf_file(), the result is displayed
    as an inline image or a PDF filename badge. Mixed content lists
    (e.g., list(ContentText("summary"), content_image_file("plot.png")))
    are rendered with items interleaved in order. (#225)

  - Added footer parameter to chat_ui() for displaying arbitrary HTML
    content below the chat input. Useful for disclaimers, attribution,
    or interactive toolbars. Styled with sensible defaults and
    customizable via --shiny-chat-footer-font-size and
    --shiny-chat-footer-color CSS custom properties. (#224)

  - Tool result cards now support a fullscreen toggle. Set full_screen =
    TRUE in the display list (or set res$full_screen <- NA in a custom
    contents_shinychat() method) to add a button that expands the card
    to fill the viewport. Press Escape, click the backdrop, or use the
    close button to exit fullscreen.

  - Added footer field to ToolResultDisplay for displaying custom HTML
    content below the tool result card body. (#178)

  - chat_mod_server() now returns a set_client(new_client, sync = TRUE)
    function for swapping the chat client used by the module at runtime.
    When sync = TRUE (the default), the new client inherits the current
    conversation's turns, system prompt, and tools so the conversation
    continues seamlessly. If a response is currently streaming, the swap
    is deferred until the stream completes. (#227)

  - chat_mod_server() now returns a status reactive that reports the
    current interaction state: "idle" when no response is in progress,
    or "streaming" while a response is actively being received. (#227)

  - chat_restore() now invisibly returns a cancel function that tears
    down all bookmark registrations made by that call. This is useful
    when swapping the chat client via set_client(), which handles the
    re-registration automatically. (#227)

Improvements

  - All navigating links in assistant messages now open in a new tab to
    preserve the app's session state. Cross-origin links still show the
    confirmation dialog; same-origin links open directly. (#238)

Bug fixes

  - Fixed the external link confirmation dialog not rendering in Safari.
    The backdrop overlay appeared but the dialog content was invisible
    due to a Bootstrap/<dialog> CSS interaction. (#201, #238)

  - Fixed pressing Escape to dismiss the external link dialog leaving it
    in a broken state where subsequent link clicks no longer worked.
    (#238)

  - Fixed an issue where user chat messages would display the default
    assistant icon. (#162)

                 Changes in version 0.3.0 (2025-11-20)                  

Breaking changes

  - chat_mod_server() now returns a list of reactives for last_input and
    last_turn, as well functions to update_user_input(), append() and
    clear() the chat. (#130, #143, #145)

New features

  - Added chat_restore() which adds Shiny bookmarking hooks to save and
    restore the {ellmer} chat client. (#28, #82)

  - Added update_chat_user_input() for programmatically updating the
    user input of a chat UI element. (#78)

  - shinychat now shows tool call request and results in the UI, and the
    feature is enabled by default in chat_app() and the chat module
    (chat_mod_server()). When using chat_append() with chat_ui(), set
    stream = "content" when you call the $stream_async() method on the
    ellmer::Chat client to ensure tool calls are included in the chat
    stream output. Learn more in the tool calling UI article. (#52)

  - Added chat_append(icon=...) and chat_ui(icon_assistant=...) for
    customizing the icon that appears next to assistant responses. (#88)

Improvements

  - chat_app() now correctly restores the chat client state when
    refreshing the app, e.g. by reloading the page. (#71)

  - External links in chat messages in chat_ui() now open in a new tab
    by default, with a confirmation dialog. (#120)

Bug fixes

  - The chat input no longer submits incomplete text when the user has
    activated IME completions (e.g. while typing in Japanese or
    Chinese). (#85)

Internal changes

  - We consolidated the <shiny-chat-message> and <shiny-user-message>
    components into a single <shiny-chat-message> component with a
    data-role attribute to indicate whether it's an "assistant" or
    "user" message. This likely has minimal impact on your apps, other
    than custom styles. You should update any shiny-user-message rules
    to use shiny-chat-message[data-role="user"]. (#101)

  - The chat UI's send input button is now identified by the class
    .shiny-chat-btn-send. (@DeepanshKhurana, #138)

                 Changes in version 0.2.0 (2025-05-16)                  

New features and improvements

  - Added new output_markdown_stream() and markdown_stream() functions
    to allow for streaming markdown content to the client. This is
    useful for showing Generative AI responses in real-time in a Shiny
    app, outside of a chat interface. (#23)

  - Both chat_ui() and output_markdown_stream() now support arbitrary
    Shiny UI elements inside of messages. This allows for gathering
    input from the user (e.g., selectInput()), displaying of rich output
    (e.g., {htmlwidgets} like {plotly}), and more. (#29)

  - Added a new chat_clear() function to clear the chat of all messages.
    (#25)

  - Added chat_app(), chat_mod_ui() and chat_mod_server(). chat_app()
    takes an {ellmer} chat client and launches a simple Shiny app
    interface with the chat. chat_mod_ui() and chat_mod_server()
    replicate the interface as a Shiny module, for easily adding a
    simple chat interface connected to a specific {ellmer} chat client.
    (#36)

  - The promise returned by chat_append() now resolves to the content
    streamed into the chat. (#49)

Bug fixes

  - chat_append(), chat_append_message() and chat_clear() now all work
    in Shiny modules without needing to namespace the id of the Chat
    component. (#37)

  - chat_append() now logs and throws a silent error if the stream
    errors for any reason. This prevents the app from crashing if the
    stream is interrupted. You can still use promises::catch() to handle
    the error in your app code if desired. (#46)

                 Changes in version 0.1.1 (2024-12-18)                  

  - Initial CRAN submission.
