Skip to content

feat(pkg-r): restore chat's displayed UI faithfully from bookmarks - #290

Open
cpsievert wants to merge 2 commits into
mainfrom
feat/chat-restore-faithful-ui
Open

feat(pkg-r): restore chat's displayed UI faithfully from bookmarks#290
cpsievert wants to merge 2 commits into
mainfrom
feat/chat-restore-faithful-ui

Conversation

@cpsievert

@cpsievert cpsievert commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • chat_restore() previously re-derived displayed chat UI from the LLM client's turns on restore. Display-only transformations applied between receiving and rendering a message could therefore be lost.
  • Server-store bookmarks now capture and replay the browser's displayed-message snapshot, preserving the UI the user saw. URL bookmarks, old bookmarks, and unreadable snapshots fall back to turn-derived UI, with a warning for unreadable saved snapshots.
  • Response bookmarking now waits for the browser's settled-message echo, so the saved snapshot includes the completed reply. It avoids startup/restore replay bookmarks, records at most one response bookmark per user submission, and preserves markdown_stream() completion bookmarking.
  • Messages passed through chat_ui(messages = ) are excluded from the snapshot because the page re-renders them on restore.

Test plan

  • npm test (974 passed)
  • Rscript -e 'devtools::test("pkg-r")' (887 passed; one browser-dependent test skipped because Chrome is unavailable)

Known follow-up

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR (stacked on #287) changes shinychat’s bookmarking/restore behavior so chat_restore() can reconstitute the exact UI that was displayed in the browser at bookmark time (for bookmarkStore = "server"), rather than re-deriving UI from LLM turns and potentially losing display-only transformations.

Changes:

  • Capture and persist a browser-reported “displayed messages” snapshot into server bookmarks and replay it on restore (with versioned decode + fallback to turn-derived UI).
  • Move response-triggered bookmarking from “stream ended” to “browser echoed a settled transcript ending in an assistant reply”.
  • Exclude page-markup messages (chat_ui(messages=)) from the reported snapshot to avoid duplication on restore; update tests/docs/dist accordingly.

Reviewed changes

Copilot reviewed 15 out of 23 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg-r/tests/testthat/test-chat_restore.R Adds unit coverage for UI snapshot codec, restore fallback, and response-bookmark observer behavior.
pkg-r/R/client_state.R Adds UI snapshot encode/decode + shared gzip/base64 codec; reuses codec for client_get_state() state.
pkg-r/R/chat_restore.R Saves/restores UI snapshots for server bookmarks; changes response-bookmark trigger to _messages echo; adds submission guard.
pkg-r/R/chat.R Updates docstring and removes stream-end bookmark chaining; clarifies history/bookmark interaction.
pkg-r/R/markdown-stream.R Removes stream-end bookmark chaining from markdown_stream().
pkg-r/NEWS.md Release notes for UI-faithful restore and page-markup message handling.
pkg-r/NAMESPACE Formatting-only import grouping changes.
pkg-r/man/chat_ui.Rd Documents that chat_ui(messages=) markup isn’t persisted and can be cleared by history replay.
pkg-r/man/chat_restore.Rd Documents server-snapshot restore behavior and fallbacks.
pkg-r/inst/lib/shiny/GIT_VERSION Updates packaged JS asset version pointer.
pkg-py/src/shinychat/www/GIT_VERSION Updates packaged JS asset version pointer for Python package.
pkg-r/DESCRIPTION Updates roxygen2 config version.
js/src/chat/state.ts Adds fromMarkup flag and excludes markup-parsed messages from reported snapshots.
js/src/chat/chat-entry.ts Marks initial markup-parsed messages as fromMarkup.
js/tests/chat/state.test.ts Tests that markup messages are excluded from buildMessagesSnapshot().
js/tests/chat/chat-entry.test.ts Tests that markup messages do not appear in the snapshot reported to Shiny.
js/tests/markdown/reservedElementsRendering.test.tsx Tests that server-built “islands” render when content is correctly labeled as HTML.
js/dist/shinychat.js Updated built bundle reflecting snapshot scope changes and other JS updates.
Files not reviewed (2)
  • pkg-r/man/chat_restore.Rd: Generated file
  • pkg-r/man/chat_ui.Rd: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg-r/R/client_state.R
Comment thread pkg-r/R/markdown-stream.R
@cpsievert
cpsievert force-pushed the feat/chat-restore-faithful-ui branch from 01f410a to 3ea2a44 Compare August 5, 2026 21:17
cpsievert added a commit that referenced this pull request Aug 5, 2026
decode_ui_snapshot() already falls back to the turn-derived UI for a
snapshot from the wrong format version, but a snapshot that decodes fine
(right version, valid JSON) and still has a malformed message -- e.g.
bit-level corruption that survives gzip/base64/serializeJSON round-tripping
intact -- would previously reach restore_history_message() and error there,
mid-replay.

That's the wrong place to catch it: restore_history_message() sends each
message to the client as it's called, so an error partway through can't be
undone by falling back afterward -- the client would end up with the first
N snapshot messages *and* the full turn-derived replay stacked on top.
Validate every message before replaying any of them instead, so a bad
snapshot routes to the fallback cleanly, same as a bad version.

Caught by Copilot's review on #290.
@cpsievert
cpsievert force-pushed the feat/chat-restore-faithful-ui branch from 50f0fe9 to 9213dff Compare August 13, 2026 01:10
@cpsievert
cpsievert changed the base branch from security/markdown-reserved-elements to main August 13, 2026 01:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants