Match renderer link routing and keep browser callbacks self-contained

Prefix only parsed issue references, preserve ordinary root-relative anchors, and avoid tsx name helpers inside the serialized DOM callback. Verify with the exact QA Node24/tsx path and positive/negative browser cases.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-29 23:28:39 -05:00
1 parent 7aa94ff745
commit 6eba7fffb4
6 files changed
+35 -15

No files matched your search

+1 -1
View File
@@ -65,7 +65,7 @@ describe("neutral decline evidence", () => {
it("checks both the visible explanation and preserved native Markdown when links become chips", () => { it("checks both the visible explanation and preserved native Markdown when links become chips", () => {
const input = native(); const input = native();
const body = "Contacts are unavailable. See [Connection outcome](/QA/issues/QA-123#document-outcome)."; const body = "Contacts are unavailable. See [Connection outcome](/QA/issues/QA-123#document-outcome).";
Object.assign(input.nativeFinalReply, { sourceBody: body, body: "Contacts are unavailable. See QA-123.", origin: "https://fixture.test", Object.assign(input.nativeFinalReply, { sourceBody: body, body: "Contacts are unavailable. See QA-123.", origin: "https://fixture.test", pageUrl: "https://fixture.test/QA/issues/QA-123",
content: [{ text: "Contacts are unavailable. See " }, { href: "/QA/issues/QA-123#document-outcome", label: "QA-123", decorated: true }, { text: "." }] }); content: [{ text: "Contacts are unavailable. See " }, { href: "/QA/issues/QA-123#document-outcome", label: "QA-123", decorated: true }, { text: "." }] });
input.nativeFinalReply.finalEvent.body = body; input.nativeFinalReply.finalEvent.body = body;
expect(passes(input)).toBe(true); expect(passes(input)).toBe(true);
+17 -2
View File
@@ -1,4 +1,6 @@
import { expect, test } from "@playwright/test"; import { expect, test } from "@playwright/test";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { readBoundTaskFinalReply } from "./native-task-reply.js"; import { readBoundTaskFinalReply } from "./native-task-reply.js";
const markdown = "I **couldn't retrieve** the pages. See [Connection outcome](/QA/issues/QA-123#document-outcome)."; const markdown = "I **couldn't retrieve** the pages. See [Connection outcome](/QA/issues/QA-123#document-outcome).";
@@ -7,6 +9,17 @@ const run = { id: "final-run", companyId: "company", agentId: "agent", status: "
resultJson: { presentationDecision: { commentId: "selected" } } }; resultJson: { presentationDecision: { commentId: "selected" } } };
const comment = { id: "selected", authorAgentId: "agent", createdByRunId: run.id, body: markdown }; const comment = { id: "selected", authorAgentId: "agent", createdByRunId: run.id, body: markdown };
test("the QA tsx loader produces a self-contained browser callback", async ({ page }) => {
const moduleUrl = new URL("./native-task-reply.ts", import.meta.url).href;
const loader = fileURLToPath(new URL("../../cli/node_modules/tsx/dist/loader.mjs", import.meta.url));
const serialized = JSON.parse(execFileSync(process.execPath, ["--import", loader, "--input-type=module", "-e",
`import { observeTaskReplyElements } from ${JSON.stringify(moduleUrl)}; console.log(JSON.stringify(observeTaskReplyElements.toString()));`], { encoding: "utf8" }));
await page.setContent(`<div data-testid='reply'>${rendered}</div>`);
const observations: any = await page.evaluate(`(${serialized})(Array.from(document.querySelectorAll('[data-testid="reply"]')))`);
expect(observations[0].body).toBe("I couldn't retrieve the pages. See QA-123.");
expect(observations[0].content.some((part: any) => part.label === "QA-123" && part.decorated)).toBe(true);
});
for (const [name, content, passes] of [ for (const [name, content, passes] of [
["decorated document link and bold text", rendered, true], ["decorated document link and bold text", rendered, true],
["ordinary link with its source label", rendered.replace(" data-mention-kind='issue'", "").replace(">QA-123</a>", ">Connection outcome</a>"), true], ["ordinary link with its source label", rendered.replace(" data-mention-kind='issue'", "").replace(">QA-123</a>", ">Connection outcome</a>"), true],
@@ -16,9 +29,9 @@ for (const [name, content, passes] of [
["extra visible text", rendered + " Extra.", false], ["extra visible text", rendered + " Extra.", false],
["hidden source text", rendered.replace("couldn't retrieve", "<span hidden>couldn't retrieve</span>"), false], ["hidden source text", rendered.replace("couldn't retrieve", "<span hidden>couldn't retrieve</span>"), false],
] as const) test(`reloaded task reply: ${name}`, async ({ page }) => { ] as const) test(`reloaded task reply: ${name}`, async ({ page }) => {
await page.route("https://fixture.test/task", route => route.fulfill({ contentType: "text/html", body: await page.route("https://fixture.test/QA/issues/QA-123", route => route.fulfill({ contentType: "text/html", body:
`<div data-testid='task-chat-thread'><div data-thread-anchor='selected'><div data-testid='task-chat-agent-bubble'>${content}</div></div></div>` })); `<div data-testid='task-chat-thread'><div data-thread-anchor='selected'><div data-testid='task-chat-agent-bubble'>${content}</div></div></div>` }));
await page.goto("https://fixture.test/task"); await page.goto("https://fixture.test/QA/issues/QA-123");
const result = readBoundTaskFinalReply({ page, runId: run.id, agentId: "agent", deadlineAt: Date.now() + (passes ? 3000 : 300), const result = readBoundTaskFinalReply({ page, runId: run.id, agentId: "agent", deadlineAt: Date.now() + (passes ? 3000 : 300),
load: async () => ({ run, comments: [comment], events: [] }) }); load: async () => ({ run, comments: [comment], events: [] }) });
if (passes) { if (passes) {
@@ -35,6 +48,8 @@ for (const [sourceHref, renderedHref, passes] of [
["#document-outcome", "/QA/issues/QA-123#document-outcome", true], ["#document-outcome", "/QA/issues/QA-123#document-outcome", true],
["/issues/QA-123#document-outcome", "/OTHER/issues/QA-123#document-outcome", false], ["/issues/QA-123#document-outcome", "/OTHER/issues/QA-123#document-outcome", false],
["#document-outcome", "/QA/issues/QA-999#document-outcome", false], ["#document-outcome", "/QA/issues/QA-999#document-outcome", false],
["/projects/example", "/projects/example", true],
["/projects/example", "/QA/projects/example", false],
] as const) test(`reloaded link ${sourceHref} to ${renderedHref}`, async ({ page }) => { ] as const) test(`reloaded link ${sourceHref} to ${renderedHref}`, async ({ page }) => {
const url = "https://fixture.test/QA/issues/QA-123"; const url = "https://fixture.test/QA/issues/QA-123";
await page.route(url, route => route.fulfill({ contentType: "text/html", body: await page.route(url, route => route.fulfill({ contentType: "text/html", body:
+1 -1
View File
@@ -78,7 +78,7 @@ it("binds complete Markdown content and the exact link destination despite a dec
const body = "I **couldn't retrieve** the pages. See [Connection outcome](/QA/issues/QA-123#document-outcome)."; const body = "I **couldn't retrieve** the pages. See [Connection outcome](/QA/issues/QA-123#document-outcome).";
const comment = { id: "selected", authorAgentId: "owned-agent", createdByRunId: "final-run", body }; const comment = { id: "selected", authorAgentId: "owned-agent", createdByRunId: "final-run", body };
const published = { ...run, resultJson: { presentationDecision: { commentId: "selected" } } }; const published = { ...run, resultJson: { presentationDecision: { commentId: "selected" } } };
const bubble: TaskReplyObservation = { ...observed, anchor: "selected", origin: "https://fixture.test", const bubble: TaskReplyObservation = { ...observed, anchor: "selected", origin: "https://fixture.test", pageUrl: "https://fixture.test/QA/issues/QA-123",
body: "I couldn't retrieve the pages. See QA-123.", body: "I couldn't retrieve the pages. See QA-123.",
content: [{ text: "I couldn't retrieve the pages. See " }, { href: "https://fixture.test/QA/issues/QA-123#document-outcome", label: "QA-123", decorated: true }, { text: "." }] }; content: [{ text: "I couldn't retrieve the pages. See " }, { href: "https://fixture.test/QA/issues/QA-123#document-outcome", label: "QA-123", decorated: true }, { text: "." }] };
expect(taskReplyBelongsToRun(bubble, published, "owned-agent", [comment])).toBe(true); expect(taskReplyBelongsToRun(bubble, published, "owned-agent", [comment])).toBe(true);
+10 -9
View File
@@ -55,21 +55,22 @@ export function uniqueBoundTaskReply(all: TaskReplyObservation[], run: Row, agen
export function observeTaskReplyElements(elements: Array<HTMLElement | SVGElement>): TaskReplyObservation[] { export function observeTaskReplyElements(elements: Array<HTMLElement | SVGElement>): TaskReplyObservation[] {
return elements.map(element => { return elements.map(element => {
const content: Array<{ text: string } | { href: string; label: string; decorated: boolean }> = []; const content: Array<{ text: string } | { href: string; label: string; decorated: boolean }> = [];
const walk = (node: Node) => { const nodes: Array<Node | string> = [element];
if (node.nodeType === Node.TEXT_NODE) { content.push({ text: node.textContent ?? "" }); return; } while (nodes.length) {
const node = nodes.pop()!;
if (typeof node === "string") { content.push({ text: node }); continue; }
if (node.nodeType === Node.TEXT_NODE) { content.push({ text: node.textContent ?? "" }); continue; }
if (!(node instanceof HTMLElement) || node.getAttribute("aria-hidden") === "true" if (!(node instanceof HTMLElement) || node.getAttribute("aria-hidden") === "true"
|| getComputedStyle(node).display === "none" || getComputedStyle(node).visibility === "hidden") return; || getComputedStyle(node).display === "none" || getComputedStyle(node).visibility === "hidden") continue;
if (node instanceof HTMLAnchorElement) { if (node instanceof HTMLAnchorElement) {
if (node.getClientRects().length && node.innerText.trim()) content.push({ href: node.href, label: node.innerText, if (node.getClientRects().length && node.innerText.trim()) content.push({ href: node.href, label: node.innerText,
decorated: node.matches('[data-mention-kind="issue"], [data-sidebar-document-link]') }); decorated: node.matches('[data-mention-kind="issue"], [data-sidebar-document-link]') });
return; continue;
} }
const block = ["P", "DIV", "BR", "LI", "PRE", "BLOCKQUOTE", "H1", "H2", "H3", "H4", "H5", "H6"].includes(node.tagName); const block = ["P", "DIV", "BR", "LI", "PRE", "BLOCKQUOTE", "H1", "H2", "H3", "H4", "H5", "H6"].includes(node.tagName);
if (block) content.push({ text: " " }); if (block) { content.push({ text: " " }); nodes.push(" "); }
node.childNodes.forEach(walk); for (let index = node.childNodes.length - 1; index >= 0; index--) nodes.push(node.childNodes[index]!);
if (block) content.push({ text: " " }); }
};
walk(element);
return { return {
body: (element as HTMLElement).innerText.trim(), body: (element as HTMLElement).innerText.trim(),
content, origin: location.origin, pageUrl: location.href, content, origin: location.origin, pageUrl: location.href,
+3 -1
View File
@@ -18,6 +18,8 @@ it("uses the production company prefix and current task URL without accepting a
content: [{ text: "Read " }, { href: "/QA/issues/QA-123#document-outcome", label: "Outcome" }] }; content: [{ text: "Read " }, { href: "/QA/issues/QA-123#document-outcome", label: "Outcome" }] };
for (const href of ["/issues/QA-123#document-outcome", "#document-outcome"]) for (const href of ["/issues/QA-123#document-outcome", "#document-outcome"])
expect(taskReplyContentMatches(observation, `Read [Outcome](${href})`)).toBe(true); expect(taskReplyContentMatches(observation, `Read [Outcome](${href})`)).toBe(true);
for (const href of ["/OTHER/issues/QA-123#document-outcome", "/QA/issues/QA-999#document-outcome", "#document-other"]) for (const href of ["/QA/issues/QA-999#document-outcome", "#document-other"])
expect(taskReplyContentMatches(observation, `Read [Outcome](${href})`)).toBe(false); expect(taskReplyContentMatches(observation, `Read [Outcome](${href})`)).toBe(false);
expect(taskReplyContentMatches({ ...observation, content: [{ text: "Read " },
{ href: "/OTHER/issues/QA-123#document-outcome", label: "Outcome" }] }, "Read [Outcome](/issues/QA-123#document-outcome)")).toBe(false);
}); });
+3 -1
View File
@@ -1,5 +1,6 @@
import { fromMarkdown } from "mdast-util-from-markdown"; import { fromMarkdown } from "mdast-util-from-markdown";
import { applyCompanyPrefix, extractCompanyPrefixFromPath } from "../../ui/src/lib/company-routes.js"; import { applyCompanyPrefix, extractCompanyPrefixFromPath } from "../../ui/src/lib/company-routes.js";
import { parseIssueReferenceFromHref } from "../../ui/src/lib/issue-reference.js";
export type ReplyContentPart = { text: string } | { href: string; label?: string; decorated?: boolean }; export type ReplyContentPart = { text: string } | { href: string; label?: string; decorated?: boolean };
type MarkdownNode = { type: string; value?: string; url?: string; children?: MarkdownNode[] }; type MarkdownNode = { type: string; value?: string; url?: string; children?: MarkdownNode[] };
@@ -9,7 +10,8 @@ function normalized(parts: ReplyContentPart[], pageUrl: string, source = false)
for (const part of parts) { for (const part of parts) {
if ("href" in part) { if ("href" in part) {
try { try {
const href = source ? applyCompanyPrefix(part.href, extractCompanyPrefixFromPath(new URL(pageUrl).pathname)) : part.href; const issueRef = source ? parseIssueReferenceFromHref(part.href) : null;
const href = issueRef ? applyCompanyPrefix(issueRef.href, extractCompanyPrefixFromPath(new URL(pageUrl).pathname)) : part.href;
result.push({ href: new URL(href, pageUrl).href, label: part.label?.replace(/\s+/g, " ").trim(), decorated: part.decorated }); result.push({ href: new URL(href, pageUrl).href, label: part.label?.replace(/\s+/g, " ").trim(), decorated: part.decorated });
} catch { return null; } } catch { return null; }
} else { } else {