Draft
Conversation
After each message completes (chunk_end or message action), the React
component calls setInputValue with {role, content, content_type}.
This allows the server to read UI message state from the client.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On bookmark save, the server asks the client for its message state via a custom message. The client responds with the serialized message array via setInputValue. On restore, the server sends a restore_messages action that bulk-loads messages into the React reducer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move UI message state ownership to the client. The React component sends each completed message as a Shiny input value, and the server accumulates them for messages(). This removes StoredMessage, _store_message, _current_stream_deps, html_deps tracking, and _transform_message. Key changes: - Server reads messages from client via _accumulate_message effect - Bookmark save reads from server-side _messages_list (no client round-trip) - Bookmark restore re-sends HTML deps and uses restore_messages action - Fix stale React state in streaming by tracking content imperatively - Remove dead _bookmark_save client protocol (server reads directly) - Remove TransformedMessage (no longer needed) - Update transform tests to reflect new behavior (messages() returns what the client echoes back, which is the transformed content) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8b71b7e to
09fe538
Compare
Without this, content_type was silently dropped when accumulating client input values into _messages_list, causing HTML messages to be misidentified as markdown on bookmark restore. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Augment hast's ElementData interface to accept the rawHtml property and fix node typing in the test's visit callback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Moves UI message state ownership to the React client. After each completed message (stream end or non-streamed), the React component calls
setInputValuewith{role, content, content_type}. The Python server accumulates these into a reactive list poweringmessages(). Bookmark save/restore for UI messages also shifts: save reads from the server's accumulated list directly (no client round-trip), and restore sends arestore_messagesreducer action to bulk-load messages on the client.What's in here
JS (commits 1–3):
setInputValuecalled on message completion (messageaction andchunk_end)restore_messagesreducer action for bookmark restorestreamingRef(fixes stale React state from batched updates)shinyChatBookmarkSaveclient protocolPython (commit 4):
messages()reads from_messages_list(accumulated from client input values via@reactive.event)_messages_list()directly; restore re-sends HTML deps viarender_depsthen dispatchesrestore_messagesStoredMessage,TransformedMessage,_store_message,_transform_message,_needs_transform,_current_stream_depstransform_user/transform_assistantparams onmessages()are now deprecated no-opsWhy this is useful
setInputValueon each message means R can readsession$input[[paste0(id, "_message")]]without building a parallel server-side accumulation system. This was the primary motivation.StoredMessage/TransformedMessagemachinery and associated tests. Simplifiesappend_message,_append_message_chunk, and bookmark save/restore.Honest downsides
messages()is no longer synchronously consistent afterappend_message(). Previously,messages()was updated immediately whenappend_messagewas called. Now there's a round-trip delay (server → client →setInputValue→ server). The dominanton_user_submit→messages()pattern isn't affected (the user message already round-tripped before the callback fires), but any code that appends and immediately reads in the same context would see stale data.System messages are silently dropped from
messages(). The old code stored system-role messages server-side even though they weren't sent to the client. Now the only path into_messages_listis the client round-trip, so system messages never appear inmessages().clear_messages()has a theoretical race. In-flightsetInputValuecalls sent before the client receives theclearaction could re-add messages to_messages_list.Server depends on client correctness. If the JS fails or
setInputValuedoesn't fire,messages()would be incomplete. The old approach had the server as its own source of truth.Transform behavior changed.
messages()now returns the transformed content (what the client echoes back) rather than the pre-transform content. This only affects the deprecated transform feature.Open question
Most of the code complexity on
maincomes from the transform machinery (TransformedMessage,content_client/content_server, etc.), not from server-side message accumulation itself. If we stripped out transforms (they're deprecated) and kept server-side storage,_store_messagewould be ~3 lines. That would preserve synchronous consistency and server-side robustness while still getting most of the simplification. The JS changes (setInputValueon completion,restore_messagesaction) are valuable either way — R benefits regardless.Whether the full client-ownership approach is worth the tradeoffs above, or whether a hybrid (Python keeps its own state, R reads from input values) is better, is worth discussing before merging.
Test plan
🤖 Generated with Claude Code