Draft
Conversation
Rewrite the chat and markdown-stream web components from Lit to React. This includes new React component architecture (ChatApp, ChatContainer, ChatInput, ChatMessage, ToolCard, ToolResult, etc.), a unified build output (shinychat.js/css), markdown processing via unified/rehype/remark, and a Shiny transport layer. Updates both R and Python packages to use the new bundled assets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add vitest with jsdom for unit/integration testing of React components, state reducer, markdown processing, transport layer, and plugins. Configure CI to run JS tests, add coverage tooling (@vitest/coverage-v8), and add shared Playwright conftest for Python tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ruff pre-commit hook configuration and ignore worktrees and docs/plans directories. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pin jsdom to ^27.x (jsdom 28's ESM-only @exodus/bytes breaks vitest 4) - Use instant scroll during streaming (smooth scroll animations get cancelled by rapid content updates, causing scroll to fall behind) - Update fullscreen test locator from <shiny-tool-result> custom element to .shiny-tool-result CSS class (React renders div, not custom element) - Fix pre-existing TS errors and prettier formatting in test files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jsdom 27.4.0 upgraded html-encoding-sniffer to ^6.0.0, which depends on the ESM-only @exodus/bytes package, breaking vitest's jsdom loader. Pin to ~27.3.0 (uses html-encoding-sniffer ^4.0.0) to avoid this. 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
This is primarily an internal refactoring — the user-facing chat UI, markdown rendering, tool cards, fullscreen mode, and external link dialog all behave identically. The Python and R packages require only minimal changes (the built JS assets consolidate from two bundles into one).
Under the hood, this rewrites the entire front-end from Lit custom elements to React, replacing ~1,750 lines of imperative DOM code with ~2,800 lines of declarative React components plus ~3,600 lines of new tests.
Motivation
The old architecture was built around three monolithic Lit classes that managed state through scattered DOM mutations, custom events, global
windowobjects, andinnerHTMLinjection viaunsafeHTML(). This made the codebase difficult to reason about, test, and extend:window.shinychat.hiddenToolRequestsSet. To understand "what is the current state of the chat?" you needed to inspect multiple class instances, thewindowobject, and the DOM itself.WeakMapto preserve custom element attributes, and special allowlists for htmlwidget scripts.What changed
Declarative rendering with centralized state
All state transitions now flow through a single
useReducerwith typed actions (chunk_start,chunk,chunk_end,clear,hide_tool_request, etc.). Components describe what the UI should look like for a given state; React figures out the minimal DOM changes. This eliminates entire categories of bugs where the DOM drifts out of sync with the logical state.Composable markdown pipeline
Replaced
marked→ DOMPurify →innerHTMLwith a unified/remark/rehype pipeline that produces React elements directly from an AST — no HTML string serialization step. Each transformation (external link annotation, code highlighting, streaming dot injection, etc.) is an independent, testable rehype plugin.Transport abstraction
Isolated all
window.Shiny.*calls behindChatTransportandShinyLifecycleinterfaces with a single implementation. React components are framework-agnostic and testable with mock transports. The legacy wire format translation is isolated to one function.Granular re-rendering
memo-wrapped components skip rendering when props haven't changed — when one message is streaming, the other messages in the conversation are untouched. The markdown pipeline uses two-stage memoization: parsing (expensive, cached on content) and rendering (cheap, re-runs only whenstreamingtoggles).Build simplification
Two separate entry points with separate CSS bundles consolidate into a single
shinychat.js+shinychat.cssbundle, simplifying the R/Python HTML dependency declarations.Comprehensive test suite
22 test files covering the state reducer, all hooks, all rehype/remark plugins, bridge components, transport layer, and integration flows. Regression tests are anchored to specific bugs. Vitest + jsdom + @testing-library/react.
Commits
Test plan
cd js && npm run lintpassescd js && npx vitest runpassesuv run pytestpasses (Python integration tests)cd pkg-r && Rscript -e "devtools::check(document = FALSE)"passes🤖 Generated with Claude Code