Skip to content

Replay the live chat's provenance marker in review transcripts - #197

Draft
cpsievert wants to merge 5 commits into
mainfrom
feat/review-provenance-aside
Draft

Replay the live chat's provenance marker in review transcripts#197
cpsievert wants to merge 5 commits into
mainfrom
feat/review-provenance-aside

Conversation

@cpsievert

@cpsievert cpsievert commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Closes #173

The trajectory review now replays the exact provenance marker the user saw
in the live chat — same <shiny-aside>, same popover — instead of rendering
a custom pill with ~140 lines of hand-maintained tooltip CSS/JS, which this
PR deletes.

  • add_message_provenance() attaches provenance_aside(tag, include_cited = TRUE)
    to each exchange's final answer as a trailing markdown chunk — the same
    shape a live-streamed aside arrives in — so shinychat's own aside grouping
    and floating-ui popover handle rendering and positioning.
  • include_cited (default FALSE, live behavior unchanged) keeps a visible
    marker for Cited answers in the review, which the live chat deliberately
    leaves bare.
  • The question list keeps its pill — it's server-rendered HTML outside
    shinychat's React tree, where <shiny-aside> would be inert — now with a
    bslib::tooltip() (parent commit).

Transcript rendering: replay, not static serialization

The transcript no longer goes through static chat_ui(messages = ...). That
path folds mixed content (tool cards plus markdown text) into a single
raw-HTML island, escaping the text and hiding it from aside grouping —
contrary to shinychat's own docs ("strings are still interpreted as markdown
as long as they're not inside HTML").

Instead, replay_transcript() mirrors shinychat's own bookmark-restore
idiom (client_set_ui()): each message's contents are yielded from a coro
generator through chat_append() once the chat element is bound, so every
chunk is classified markdown vs HTML exactly as in the live stream. Tool
cards render as shinychat's native tool groups, htmlwidget dependencies are
collected per chunk, and thinking blocks keep their styling — none of which
held under static serialization. Exchange decorations are seeded after the
replay in the same flush, so message indices line up.

A static-chat_ui() fix upstream in shinychat would still be welcome, but
this PR no longer depends on or works around that path.

Validation

test-trajectory-review.R, test-provenance.R, and test-chat.R pass,
including coverage that transcripts carry the aside marker as a trailing
markdown chunk for A and B outcomes. Full suite left to CI.

Verified manually in a local review app with fabricated trajectories
covering every provenance outcome plus a run_sql tool call (example app
kept local, not in this PR):

  • All three aside markers render as shinychat's native grouped pills, with
    working popovers (opaque background; the transparency issue seen with the
    old hand-rolled CSS is gone).
  • The tool call renders as shinychat's native tool group in the same
    message flow as the aside markers.
  • Exchange selection syncs in both directions: transcript click updates the
    review pane and sidebar highlight, and sidebar clicks highlight the
    corresponding transcript messages.

Browser/live-warehouse tests not run.

The question list is server-rendered HTML outside shinychat's React
tree, so bslib (Popper, container = body) can own tooltip positioning
there. commons_answer_pill() gains a tooltip argument; the transcript
keeps the inline CSS tooltip for now since bslib tooltips read
template.content, which React-rendered message content never populates.
The trajectory review rebuilt provenance markers as a custom pill with
a hand-rolled CSS tooltip, plus commons-chat.js to nudge the tooltip
back inside the chat pane when it clipped. shinychat already solves
this: its aside popover opens on hover/focus/click and is positioned
by floating-ui through a portal, escaping clipping ancestors entirely.

add_message_provenance() now appends the same <shiny-aside> string the
live chat streams, as plain markdown joined with a blank line — the
shape contents_shinychat() produces for a live-streamed aside — so the
client's aside grouping turns it into the same marker users saw. A new
include_cited flag on provenance_aside() keeps a visible marker for
Cited answers in the review, which the live chat deliberately leaves
bare. Deleted: commons-chat.js, commons_pill_tooltip(), and the
.commons-tooltip CSS.

Mixed message content (tool cards plus text) is flattened to a single
markdown string per message: shinychat's static chat_ui() serializer
folds mixed lists into one raw-HTML island, escaping the text and
hiding it from aside grouping. The flattening keeps tool cards as
routable custom elements; a shinychat-side fix would let us drop it.
The pill's visible text already announces the label, Bootstrap sets
aria-describedby when the bslib tooltip shows the body, and the icon's
alt text was a third copy. The icon is now decorative (alt="").
commons-chat.js is gone and the figs/ SVGs moved under
www/commons-chat; refresh the files block and checksums accordingly.
A full writeManifest() regeneration needs an environment with
GitHub-sourced ellmer/shiny/shinychat installs; the packages section
is unchanged.
Static chat_ui(messages =) folds mixed content (tool cards plus markdown
text) into a single raw-HTML island, escaping the text and hiding it from
shinychat's aside grouping. Mirror shinychat's own bookmark-restore idiom
(client_set_ui): yield each message's contents from a coro generator
through chat_append(), so every chunk is classified markdown vs HTML
exactly as in the live stream. This drops the flatten_message_content()
workaround along with its caveats (uncollected htmlwidget dependencies,
unstyled thinking blocks), and the provenance aside now lands as a
trailing markdown chunk, matching how a live-streamed aside arrives.
@cpsievert
cpsievert force-pushed the feat/review-provenance-aside branch from d0dd412 to 811a465 Compare August 27, 2026 23:52
@github-actions

Copy link
Copy Markdown

Preview deployed to Connect (dogfood.team.pct.posit.it): https://dogfood.team.pct.posit.it/connect/#/apps/d7a36cae-8f27-448b-a478-61b81fbe3942/draft/365436

Deployed from commit 811a465.

@github-actions

Copy link
Copy Markdown

Preview deployed to Connect (connect.staging.pct.posit.it): https://connect.staging.pct.posit.it/connect/#/apps/ad662e1b-5048-4acc-9ad7-f9478c92274e/draft/2329

Deployed from commit 811a465.

@cpsievert
cpsievert marked this pull request as draft August 28, 2026 00:06
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.

Eliminate the JS layer

1 participant