Skip to content

feat(docs): copy markdown + share-link in ask widget#23294

Queued
AztecBot wants to merge 3 commits into
nextfrom
cb/bc72b2264b41
Queued

feat(docs): copy markdown + share-link in ask widget#23294
AztecBot wants to merge 3 commits into
nextfrom
cb/bc72b2264b41

Conversation

@AztecBot
Copy link
Copy Markdown
Collaborator

Mirrors the two features just landed in honk-ai#131 over to AztecDocsWidget, which is what docs.aztec.network actually ships (honk-ai's /ask is a separate frontend). Same UX, same security posture.

What it does

1. Copy raw markdown

Small Copy chip on each assistant message header. Writes the raw markdown text (with backticks, headers, lists, fences) to the clipboard so it survives a paste into Slack/Notion/GitHub. Uses the async Clipboard API where available and falls back to a hidden <textarea> + document.execCommand("copy") for sandboxed iframes / older WebViews.

2. Share link

Share button in the panel header (visible once the conversation has at least one finished reply). It encodes the current Q&A as JSON → UTF-8 → gzip (CompressionStream) → base64url, slots it into the URL hash as …/<page>#share=<blob>, and copies the link.

When someone opens that link:

  • the widget detects #share=… on mount, decodes locally, replays the transcript, and auto-opens the panel
  • a status banner ("Viewing a shared conversation. Start a new one") appears at the top of the panel
  • as soon as the recipient hits send (or clicks reset), the banner clears and the hash is scrubbed via history.replaceState so a refresh doesn't reload the old transcript over their new one

The encoded blob never leaves the browser — URL fragments are not sent to the origin, so this preserves the existing anonymous-no-persist posture (no DB, no new endpoints, no backend touch). A roundtrip of a small conversation lands around ~280 bytes; the codec hard-caps encoded output at 32 KiB.

Implementation notes

  • New module: docs/src/components/AztecDocsWidget/share.js — versioned wire format { v: 1, m: [{ r: "u"|"b", t, s? }] }, plus readShareHash / buildShareUrl / clearShareHash helpers and a shared copyToClipboard helper reused by the Copy button.
  • index.jsx — hash-replay useEffect on mount that pairs flat {role,text} entries back into the widget's {prompt, response, sources} message shape; handleShare callback; clears viewingShared / hash on new handleSend or handleReset.
  • Panel.jsx — Share button in the header (styled inline to read theme tokens, with .azw-share-btn.is-ok / .is-fail overrides for the post-click state); shared-conversation banner above the body.
  • Message.jsxCopyMarkdownButton rendered in the assistant role header on completed (non-streaming, non-empty) replies, after the existing feedback wiring from feat(docs): user feedback + surfaced error frames in docs ask widget #23288.
  • styles.css — minimal additions for the share-button success/fail states.

Test plan

  • yarn preprocess && yarn docusaurus build — clean (the two broken-anchor warnings are pre-existing in the CLI reference, unrelated to this change)
  • Babel parse-check on all four touched JSX files + node --check on share.js
  • Node roundtrip of the share codec over a sample 4-message conversation (encodes to ~280 bytes, decodes back to identical structure, malformed token returns null)
  • Manual browser smoke: ask a question, click Copy, paste to verify markdown survives; click Share, open the copied URL in a new tab, verify the panel auto-opens with the banner; hit send and verify the banner clears + hash is scrubbed

Created by claudebox · group: slackbot

@AztecBot AztecBot added the claudebox Owned by claudebox. it can push to this PR. label May 14, 2026
- Sanitize source hrefs in fromWire so a crafted `#share=` payload can't
  inject javascript:/data:/protocol-relative URLs into the anchor tags
  rendered by Message.jsx.
- Cap encoded token at 32 KiB on decode and stream decompression with a
  256 KiB inflated-output cap to neutralise gzip bombs.
- Track userInteractedRef so a slow share-decode resolving after the user
  opens the launcher / sends / resets can't clobber the local conversation.
- Treat window.prompt cancel as failed copy instead of flashing 'Link copied'.
- Move textarea cleanup into finally so a throw during execCommand can't
  leak the hidden node into the DOM.
- Drop the no-op gzip fallback and the stale Feedback banner comment.
The WHATWG URL parser normalizes backslashes to forward slashes for
special schemes, so inputs like \\evil/x, /\\evil/x, and
https:/\\evil/x round-trip through new URL() into external origins.
Reject any href containing a backslash so the rendered anchor href
never reaches the browser parser with a host-injecting payload.
@critesjosh critesjosh marked this pull request as ready for review May 15, 2026 01:18
@critesjosh critesjosh enabled auto-merge May 15, 2026 01:18
@critesjosh critesjosh added this pull request to the merge queue May 15, 2026
Any commits made after this event will not be merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claudebox Owned by claudebox. it can push to this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants