Repository navigation
Description
Summary
Selecting and copying a GitHub warning callout in web chat turns it into ordinary paragraphs: the clipboard contains the renderer's “Warning” label and the body, but loses the warning marker and quote structure. Text selected from a message is documented to copy as readable Markdown, so the pasted content should preserve the callout's meaning. A focused serializer run reproduced the output, and a parser run confirmed the warning input reaches the callout renderer.
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/web
Steps to reproduce
- In web chat, render a message containing
> [!WARNING]on one line and> Back up first.on the next. - Select the complete warning callout and copy the selection; inspect the clipboard's plain text. For a checkout-only check, save the script below as
repro.shand run it with the checkout path.
The serializer fixture failed once in one run. The ordinary blockquote control passed. Separately, parsing the warning input with the repository's remarkGithubAlerts plugin produced {"type":"blockquote","alert":"warning","text":"Back up first."}.
The script requires the checkout's installed dependencies, Node, and vp. It creates one temporary test file named markdown-clipboard.y06-02.test.ts, runs only that test, and removes the file and its scratch directory on exit. Status 1 with RESULT: defect reproduced means the control passed and the alert copy differs from its source; status 0 means the expected alert Markdown was copied; status 2 means the setup or control was inconclusive. Run sh repro.sh /path/to/t3code-checkout.
Reproduction script: repro.sh
#!/bin/sh
set -eu
checkout=${1:?usage: repro.sh CHECKOUT}
scratch=$(mktemp -d)
test_file="$checkout/apps/web/src/markdown-clipboard.y06-02.test.ts"
cleanup() {
rm -f "$test_file"
rm -rf "$scratch"
}
trap cleanup EXIT HUP INT TERM
printf 'node: '
node --version
printf 'vp: '
vp --version
cat > "$test_file" <<'TEST'
import { expect, it, vi } from "vite-plus/test";
import { serializeRenderedMarkdownFragment } from "./markdown-clipboard";
class TextNode {
readonly nodeType = 3;
readonly childNodes: never[] = [];
constructor(readonly textContent: string) {}
}
class ElementNode {
readonly nodeType = 1;
readonly childNodes: Array<TextNode | ElementNode> = [];
readonly classList = { contains: () => false };
constructor(readonly tagName: string) {}
get localName() { return this.tagName.toLowerCase(); }
get textContent() { return this.childNodes.map(child => child.textContent).join(""); }
getAttribute(_name: string) { return null; }
hasAttribute(_name: string) { return false; }
append(...children: Array<TextNode | ElementNode>) { this.childNodes.push(...children); return this; }
}
it("copies ordinary quotes but loses a rendered warning's alert marker", () => {
vi.stubGlobal("Node", { TEXT_NODE: 3, ELEMENT_NODE: 1 });
try {
const control = new ElementNode("DIV").append(
new ElementNode("BLOCKQUOTE").append(new ElementNode("P").append(new TextNode("Back up first."))),
);
const alert = new ElementNode("DIV").append(
new ElementNode("DIV").append(
new ElementNode("P").append(new TextNode("Warning")),
new ElementNode("P").append(new TextNode("Back up first.")),
),
);
const plain = serializeRenderedMarkdownFragment(control as unknown as Node);
const copied = serializeRenderedMarkdownFragment(alert as unknown as Node);
console.log(`CONTROL=${JSON.stringify(plain)}`);
console.log(`ALERT_COPY=${JSON.stringify(copied)}`);
expect(plain).toBe("> Back up first.");
expect(copied).toBe("> [!WARNING]\n> Back up first.");
} finally {
vi.unstubAllGlobals();
}
});
TEST
set +e
(cd "$checkout" && HOME="$scratch" TMPDIR="$scratch" vp test run apps/web/src/markdown-clipboard.y06-02.test.ts) > "$scratch/test.out" 2>&1
status=$?
set -e
grep -E 'CONTROL=|ALERT_COPY=|AssertionError|expected|Test Files|Tests ' "$scratch/test.out" || cat "$scratch/test.out"
if [ "$status" -eq 0 ]; then
printf 'RESULT: expected behavior\n'
exit 0
fi
if grep -q 'CONTROL="> Back up first."' "$scratch/test.out" && grep -q 'ALERT_COPY="Warning\\n\\nBack up first."' "$scratch/test.out"; then
printf 'RESULT: defect reproduced\n'
exit 1
fi
printf 'RESULT: inconclusive\n'
exit 2Expected behavior
Text copied from a message should remain readable Markdown, as the docs/user/composer.md#L373-L375 says. For a warning callout, the copied plain text should retain a warning marker and its association with the body, such as > [!WARNING]\n> Back up first.; the rendered title alone does not encode that structure. A normal blockquote already copies with quote prefixes in apps/web/src/markdown-clipboard.ts#L163-L173.
Actual behavior
For > [!WARNING] followed by > Back up first., selecting the whole rendered callout and copying it reaches apps/web/src/components/ChatMarkdown.tsx#L675-L681 and apps/web/src/markdown-clipboard.ts#L442-L487. The focused run of the same rendered element shape returned Warning\n\nBack up first.. The warning marker and blockquote prefix were absent; “Warning” is a label inserted by the renderer rather than message text. The observed bytes are from the serializer fixture; the full browser clipboard action was traced in source, not exercised in a running client.
Impact and scope
Impact: P2 - a bounded clipboard content defect changes the meaning and structure of a copied warning, but the body remains available and the user can repair the pasted Markdown manually.
The confirmed path is web chat selection copy for a full GitHub warning callout. The same renderer handles the other GitHub alert types and Claude Insight callouts, so their selected-copy behavior follows the same branches; those variants were not separately run. This does not assess the message copy button or mobile copy behavior. Repeating the same selection follows the same serializer path.
Earliest failure stage
Output or presentation.
Analysis or suspected cause
Source trace: The parser recognizes the input as a warning and removes the authored marker before rendering: packages/shared/src/markdownGithubAlerts.ts#L10-L42, registered in packages/shared/src/markdownPipeline.ts#L246-L258. The renderer creates a div role="note" and a label paragraph, not a blockquote: apps/web/src/components/ChatMarkdown.tsx#L1057-L1079. The serializer has a BLOCKQUOTE branch but sends that div and its label through generic paragraph and DIV branches: apps/web/src/markdown-clipboard.ts#L212-L285. The copy handler writes this result to text/plain: apps/web/src/components/ChatMarkdown.tsx#L675-L681.
Additional context
- Upstream
mainstill has no alert-specific serializer branch, and its callout renderer still emits the same labeleddiv.
Evidence provenance: the serializer output and parser result were observed in focused runs; the browser clipboard path was traced in source. Local verification: reproduced in the serializer fixture. Reproduction completeness: the fixture is complete for the serializer output, while the interactive browser copy action was not exercised. The restoration check is the script's warning assertion passing alongside its ordinary blockquote control.
Impact
Minor bug or occasional failure
Version or commit
main @ c00a373
Environment
Darwin arm64; Node v26.12.1; vp v1.5.1 with local vite-plus v1.4.0. The serializer check ran in the repository's unit test environment.
Logs or stack traces
CONTROL="> Back up first."
ALERT_COPY="Warning\n\nBack up first."
AssertionError: expected 'Warning\n\nBack up first.' to be '> [!WARNING]\n> Back up first.'
Test Files 1 failed (1)
Tests 1 failed (1)
RESULT: defect reproducedScreenshots, recordings, or supporting files
No response
Workaround
Repair the pasted Markdown manually by restoring the warning marker and quote prefixes.
References
- Copying a command from an assistant code block includes Markdown fences #23930 — a closed issue about copied code fences in the same selection-copy path; it does not report callouts.
- fix(web): render Claude insight blocks as a callout #22239 — introduced Insight callout rendering using the same label and container; it does not address selection copy.
Activity