fix(session-ui): render the link globe as its own element and drop it once the favicon loads (#61417)

This commit is contained in:
opencode-agent[bot]
2026-11-03 14:36:41 +00:00
committed by GitHub
Unverified
parent 6d1c444b8c
commit ce8ca3084e
3 changed files with 21 additions and 8 deletions
@@ -1,4 +1,4 @@
import type { Page } from "@playwright/test"
import type { Locator, Page } from "@playwright/test"
import { expect, sourceURL, story } from "../../stories/playwright/story"
const png = Buffer.from(
@@ -318,8 +318,9 @@ story("shows a stable GitHub mark without changing link text or other sites", as
await expect(github).toHaveAttribute("href", "https://github.com/anomalyco/opencode/pull/540")
await expect(github).toHaveText("#540")
const mark = github.locator(".markdown-link-favicon[data-github]")
expect(await mark.evaluate((icon) => getComputedStyle(icon, "::before").width)).toBe("14px")
expect(await mark.evaluate((icon) => getComputedStyle(icon, "::before").maskImage)).toContain("data:image/svg+xml")
const glyph = mark.locator(".markdown-link-favicon-glyph")
expect(await glyph.evaluate((icon) => getComputedStyle(icon).width)).toBe("14px")
expect(await glyph.evaluate((icon) => getComputedStyle(icon).maskImage)).toContain("data:image/svg+xml")
await expect(mark.locator("img")).toHaveCount(0)
await expect(markdown.getByRole("link", { name: "GitHub" }).locator(".markdown-link-favicon[data-github]")).toHaveCount(
1,
@@ -360,8 +361,10 @@ story("keeps favicon space stable across loading and failure without fetching pr
await expect(markdown).toHaveAttribute("data-markdown-ready", "")
const docs = markdown.getByRole("link", { name: "docs" })
const image = docs.locator(".markdown-link-favicon img")
const globe = (link: Locator) => link.locator(".markdown-link-favicon-glyph")
await expect.poll(() => requested.some((url) => url.includes("developer.mozilla.org"))).toBe(true)
await expect(image).toHaveCSS("opacity", "0")
await expect(globe(docs)).toBeVisible()
expect((await docs.locator(".markdown-link-favicon").boundingBox())?.width).toBe(14)
// A late web font swap also changes the link width; measure after fonts settle.
await page.evaluate(() => document.fonts.ready.then(() => undefined))
@@ -369,6 +372,7 @@ story("keeps favicon space stable across loading and failure without fetching pr
release()
await expect(image).toHaveAttribute("data-loaded", "")
await expect(image).toHaveCSS("opacity", "1")
await expect(globe(docs)).toHaveCount(0)
await expect(image).not.toHaveAttribute("role", "button")
expect(await image.evaluate((favicon) => favicon.onclick)).toBeNull()
expect(await docs.evaluate((link) => link.getBoundingClientRect().width)).toBe(width)
@@ -384,6 +388,7 @@ story("keeps favicon space stable across loading and failure without fetching pr
await expect(
markdown.getByRole("link", { name: "missing" }).locator(".markdown-link-favicon img"),
).not.toHaveAttribute("data-loaded", "")
await expect(globe(markdown.getByRole("link", { name: "missing" }))).toBeVisible()
await expect(markdown.getByRole("link", { name: "private" }).locator(".markdown-link-favicon img")).toHaveCount(0)
await expect(markdown.getByRole("link", { name: "GitHub" }).locator(".markdown-link-favicon img")).toHaveCount(0)
expect(requested).toHaveLength(2)
@@ -153,8 +153,7 @@
margin-inline-end: 4px;
}
.markdown-link-favicon::before {
content: "";
.markdown-link-favicon-glyph {
position: absolute;
inset: 0;
height: 14px;
@@ -163,7 +162,7 @@
mask: url("./markdown-globe.svg") center / contain no-repeat;
}
.markdown-link-favicon[data-github]::before {
.markdown-link-favicon[data-github] .markdown-link-favicon-glyph {
mask-image: url("./markdown-github.svg");
}
@@ -361,6 +361,9 @@ function markExternalLinkFavicons(root: HTMLDivElement) {
favicon.className = "markdown-link-favicon"
favicon.setAttribute("aria-hidden", "true")
const host = url.hostname.toLowerCase()
const glyph = document.createElement("span")
glyph.className = "markdown-link-favicon-glyph"
favicon.appendChild(glyph)
if (host === "github.com") favicon.dataset.github = ""
else if (
@@ -383,13 +386,19 @@ function markExternalLinkFavicons(root: HTMLDivElement) {
})
}
// The globe stands in only until the favicon loads; removing it keeps transparent favicons from showing it through.
function showFavicon(image: HTMLImageElement) {
image.dataset.loaded = ""
image.parentElement?.querySelector(".markdown-link-favicon-glyph")?.remove()
}
function setupExternalLinkFavicons(root: HTMLDivElement) {
const loaded = (event: Event) => {
const image = event.target
if (!(image instanceof HTMLImageElement) || !image.hasAttribute("data-markdown-favicon")) return
if (image.naturalWidth > 0) image.dataset.loaded = ""
if (image.naturalWidth > 0) showFavicon(image)
}
root.addEventListener("load", loaded, true)
@@ -901,7 +910,7 @@ export function Markdown(
images?.update(container)
previewImages(container)
container.querySelectorAll<HTMLImageElement>("img[data-markdown-favicon]").forEach((image) => {
if (image.complete && image.naturalWidth > 0) image.dataset.loaded = ""
if (image.complete && image.naturalWidth > 0) showFavicon(image)
})
// A button gets the labels of the run that creates it, so the others only need new ones when the language changes.