fix(desktop): align web search results presentation with figma (#44039)

This commit is contained in:
Luke Parker
2026-08-22 13:22:22 +10:00
committed by GitHub
parent a84b1c15ce
commit e7177a8764
8 changed files with 284 additions and 83 deletions
@@ -116,7 +116,7 @@
letter-spacing: var(--letter-spacing-normal);
color: var(--v2-text-text-muted);
&.clickable {
&.clickable:not(.webfetch-link) {
cursor: pointer;
text-decoration: underline;
transition: color 0.15s ease;
@@ -145,62 +145,6 @@
color: var(--text-interactive-base);
}
}
&.webfetch-link {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--v2-text-text-accent);
text-decoration: none;
&:visited,
&:active {
color: var(--v2-text-text-accent);
}
[data-slot="webfetch-link-text"] {
text-decoration: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.webfetch-link-icon {
display: none;
width: 16px;
height: 16px;
flex-shrink: 0;
color: var(--v2-icon-icon-accent, var(--v2-text-text-accent));
}
&:hover {
color: var(--v2-text-text-accent);
text-decoration: none;
[data-slot="webfetch-link-text"] {
text-decoration: underline;
text-underline-offset: 2px;
}
.webfetch-link-icon {
display: inline-flex;
}
}
&:focus-visible {
outline: 1px solid var(--v2-text-text-accent);
outline-offset: 2px;
[data-slot="webfetch-link-text"] {
text-decoration: underline;
text-underline-offset: 2px;
}
.webfetch-link-icon {
display: inline-flex;
}
}
}
}
[data-slot="basic-tool-tool-arg"] {
@@ -381,3 +325,72 @@
}
}
}
.webfetch-link,
[data-slot="basic-tool-tool-subtitle"].webfetch-link,
[data-slot="exa-tool-link"].webfetch-link,
[data-component="tool-trigger"] [data-slot="basic-tool-tool-subtitle"].webfetch-link {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--v2-text-text-accent);
text-decoration: none;
overflow: visible;
max-width: 100%;
font-family: var(--font-family-sans);
font-variant-numeric: tabular-nums;
font-size: inherit;
font-style: normal;
font-weight: var(--font-weight-regular, 440);
line-height: inherit;
letter-spacing: var(--letter-spacing-normal);
&:visited,
&:active {
color: var(--v2-text-text-accent);
}
[data-slot="webfetch-link-text"] {
text-decoration: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: inherit;
}
.webfetch-link-icon {
display: none;
width: 16px;
height: 16px;
flex-shrink: 0;
color: var(--v2-icon-icon-accent, var(--v2-text-text-accent));
}
&:hover {
color: var(--v2-text-text-accent);
text-decoration: none;
[data-slot="webfetch-link-text"] {
text-decoration: underline;
text-underline-offset: 2px;
}
.webfetch-link-icon {
display: inline-flex;
}
}
&:focus-visible {
outline: 1px solid var(--v2-text-text-accent);
outline-offset: 2px;
[data-slot="webfetch-link-text"] {
text-decoration: underline;
text-underline-offset: 2px;
}
.webfetch-link-icon {
display: inline-flex;
}
}
}
@@ -1,5 +1,7 @@
import { createSignal, For, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import { BasicTool } from "./basic-tool"
export default {
@@ -73,3 +75,108 @@ export const Controlled = {
)
},
}
export const WebFetch = {
render: () => (
<BasicTool
icon="window-cursor"
hideDetails
trigger={
<div data-slot="basic-tool-tool-info-structured">
<div data-slot="basic-tool-tool-info-main">
<span data-slot="basic-tool-tool-title">Webfetch</span>
<a
data-slot="basic-tool-tool-subtitle"
class="webfetch-link"
href="https://www.figma.com"
target="_blank"
rel="noopener noreferrer"
>
<span data-slot="webfetch-link-text">https://www.figma.com</span>
<Icon name="outline-square-arrow" class="webfetch-link-icon" />
</a>
</div>
</div>
}
/>
),
}
const mockSearchUrls = [
"https://www.figma.com/community/file/1606560040358762787/figma-mcp-console-setup-guide",
"https://designagentlab.com",
"https://www.figma.com/community/whiteboarding?resource_type=widgets",
"https://figma-console-mcp.southleft.com/mcp",
"https://designagentlab.com/figma-console-mcp",
"https://designagentlab.com/figma-tutorials",
"https://github.com/southleft/figma-console-mcp/issues",
"https://designagentlab.com/ui-kits",
"https://designagentlab.com/prototyping-tools",
"https://www.inthepocket.design/guidelines/figma-mcp/setup-figma-mcp",
"https://www.figma.com/community/plugins",
"https://figma-console-mcp.southleft.com/docs",
"https://designagentlab.com/resources",
"https://github.com/southleft/figma-console-mcp/releases",
"https://www.inthepocket.design/blog/figma-mcp",
"https://designagentlab.com/community",
]
export const WebSearch = {
render: () => {
const [showAll, setShowAll] = createSignal(false)
let firstRevealedRef: HTMLAnchorElement | undefined
const visibleLinks = () => (showAll() ? mockSearchUrls : mockSearchUrls.slice(0, 10))
const remaining = () => Math.max(0, mockSearchUrls.length - 10)
const expand = (event: MouseEvent) => {
event.stopPropagation()
setShowAll(true)
requestAnimationFrame(() => {
firstRevealedRef?.focus()
})
}
return (
<BasicTool
icon="window-cursor"
defaultOpen
trigger={{
title: "Firecrawl Web Search",
subtitle: "figma mcp setup",
subtitleClass: "exa-tool-query",
}}
>
<div data-component="exa-tool-output">
<div data-slot="exa-tool-rail">
<div data-slot="exa-tool-rail-line" />
</div>
<div data-slot="exa-tool-links">
<For each={visibleLinks()}>
{(url, index) => (
<a
ref={(el) => {
if (index() === 10) firstRevealedRef = el
}}
data-slot="exa-tool-link"
class="webfetch-link"
href={url}
target="_blank"
rel="noopener noreferrer"
onClick={(event) => event.stopPropagation()}
>
<span data-slot="webfetch-link-text">{url}</span>
<Icon name="outline-square-arrow" class="webfetch-link-icon" />
</a>
)}
</For>
<Show when={!showAll() && remaining() > 0}>
<button type="button" data-slot="exa-tool-more" onClick={expand}>
+{remaining()} more
</button>
</Show>
</div>
</div>
</BasicTool>
)
},
}
@@ -569,15 +569,31 @@
}
[data-component="exa-tool-output"] {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
min-width: 0;
font-family: var(--font-family-sans);
font-size: var(--font-size-base);
line-height: var(--line-height-large);
font-size: 13px;
line-height: 16px;
color: var(--v2-text-text-muted);
}
[data-slot="exa-tool-rail"] {
display: flex;
align-items: center;
align-self: stretch;
flex-shrink: 0;
padding-bottom: 12px;
padding-inline-start: 12px;
padding-inline-end: 16px;
}
[data-slot="exa-tool-rail-line"] {
width: 0.5px;
height: 100%;
background-color: var(--v2-border-border-muted, rgba(0, 0, 0, 0.08));
}
[data-slot="basic-tool-tool-subtitle"].exa-tool-query {
display: block;
max-width: 100%;
@@ -589,27 +605,39 @@
[data-slot="exa-tool-links"] {
display: flex;
flex-direction: column;
gap: 4px;
align-items: flex-start;
gap: 8px;
min-width: 0;
flex: 1 0 0;
}
[data-slot="exa-tool-link"] {
display: block;
width: fit-content;
max-width: 100%;
font: inherit;
line-height: inherit;
color: var(--v2-text-text-accent);
text-decoration: underline;
text-underline-offset: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
align-self: flex-start;
}
[data-slot="exa-tool-more"] {
all: unset;
cursor: pointer;
width: fit-content;
font-family: var(--font-family-sans);
font-size: 13px;
font-weight: var(--font-weight-regular, 440);
line-height: 16px;
letter-spacing: -0.04px;
color: var(--v2-text-text-faint, #808080);
user-select: none;
&:hover {
color: var(--v2-text-text-accent);
color: var(--v2-text-text-muted);
text-decoration: underline;
text-underline-offset: 2px;
}
&:visited {
color: var(--v2-text-text-accent);
&:focus-visible {
outline: 1px solid var(--v2-text-text-accent);
outline-offset: 2px;
}
}
@@ -702,15 +702,32 @@ export const webResearchDocument = document([
id: "tool_web_search",
name: "websearch",
offset: 73_100,
args: { query: "WAI ARIA live region status message guidance" },
output: "WAI-ARIA Authoring Practices and MDN live region guidance",
metadata: { provider: "exa" },
args: { query: "figma mcp setup" },
output: [
"https://www.figma.com/community/file/1606560040358762787/figma-mcp-console-setup-guide",
"https://designagentlab.com",
"https://www.figma.com/community/whiteboarding?resource_type=widgets",
"https://figma-console-mcp.southleft.com/mcp",
"https://designagentlab.com/figma-console-mcp",
"https://designagentlab.com/figma-tutorials",
"https://github.com/southleft/figma-console-mcp/issues",
"https://designagentlab.com/ui-kits",
"https://designagentlab.com/prototyping-tools",
"https://www.inthepocket.design/guidelines/figma-mcp/setup-figma-mcp",
"https://www.figma.com/community/plugins",
"https://figma-console-mcp.southleft.com/docs",
"https://designagentlab.com/resources",
"https://github.com/southleft/figma-console-mcp/releases",
"https://www.inthepocket.design/blog/figma-mcp",
"https://designagentlab.com/community",
].join("\n"),
metadata: { provider: "firecrawl" },
}),
completedTool({
id: "tool_web_fetch",
name: "webfetch",
offset: 74_000,
args: { url: "https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html" },
args: { url: "https://www.figma.com" },
output: "Status messages should be programmatically determinable without receiving focus.",
}),
],
@@ -412,25 +412,55 @@ function taskSession(
}
function ExaOutput(props: { output?: string }) {
const i18n = useI18n()
const [showAll, setShowAll] = createSignal(false)
let firstRevealedRef: HTMLAnchorElement | undefined
const links = createMemo(() => urls(props.output))
const visibleLinks = createMemo(() => {
const all = links()
if (showAll() || all.length <= 10) return all
return all.slice(0, 10)
})
const remaining = createMemo(() => Math.max(0, links().length - 10))
const expand = (event: MouseEvent) => {
event.stopPropagation()
setShowAll(true)
requestAnimationFrame(() => {
firstRevealedRef?.focus()
})
}
return (
<Show when={links().length > 0}>
<div data-component="exa-tool-output">
<div data-slot="exa-tool-rail">
<div data-slot="exa-tool-rail-line" />
</div>
<div data-slot="exa-tool-links">
<For each={links()}>
{(url) => (
<For each={visibleLinks()}>
{(url, index) => (
<a
ref={(el) => {
if (index() === 10) firstRevealedRef = el
}}
data-slot="exa-tool-link"
class="webfetch-link"
href={url}
target="_blank"
rel="noopener noreferrer"
onClick={(event) => event.stopPropagation()}
>
{url}
<span data-slot="webfetch-link-text">{url}</span>
<Icon name="outline-square-arrow" class="webfetch-link-icon" />
</a>
)}
</For>
<Show when={!showAll() && remaining() > 0}>
<button type="button" data-slot="exa-tool-more" onClick={expand}>
{i18n.plural("ui.common.moreCount", remaining())}
</button>
</Show>
</div>
</div>
</Show>
@@ -898,7 +928,7 @@ ToolRegistry.register({
<Show when={!pending() && url()}>
<a
data-slot="basic-tool-tool-subtitle"
class="clickable webfetch-link"
class="webfetch-link"
href={url()}
target="_blank"
rel="noopener noreferrer"
+1 -1
View File
@@ -35,7 +35,7 @@ function CollapsibleArrow(props?: ComponentProps<"div">) {
return (
<div data-slot="collapsible-arrow" {...(props || {})}>
<span data-slot="collapsible-arrow-icon">
<Icon name="chevron-down" size="small" />
<Icon name="fill-triangle-down" size="small" />
</span>
</div>
)
+2
View File
@@ -188,6 +188,8 @@ const source = {
"ui.common.next": "Next",
"ui.common.submit": "Submit",
"ui.common.showMore": "Show more",
"ui.common.moreCount.one": "+{{count}} more",
"ui.common.moreCount.other": "+{{count}} more",
"ui.permission.deny": "Deny",
"ui.permission.allowAlways": "Allow always",
+4
View File
@@ -160,6 +160,10 @@ const icons = {
viewBox: "0 0 20 20",
body: `<path d="M5.83333 4.16406L2.5 7.4974L5.83333 10.8307M3.33333 7.4974H17.9167V15.4141H10" stroke="currentColor" stroke-linecap="square"/>`,
},
"fill-triangle-down": {
viewBox: "0 0 16 16",
body: `<path d="M5.37624 6.75194C5.1818 6.41861 5.42223 6 5.80813 6H10.1921C10.578 6 10.8184 6.41861 10.624 6.75194L8.43199 10.5096C8.23905 10.8404 7.76115 10.8404 7.56821 10.5096L5.37624 6.75194Z" fill="currentColor"/>`,
},
archive: {
viewBox: "0 0 16 16",
body: `<path d="M13.1112 13.5555V14.0555H13.6112V13.5555H13.1112ZM2.889 13.5555H2.389L2.389 14.0555H2.889V13.5555ZM3.38901 5.55546L3.38901 5.05546L2.38901 5.05546L2.38901 5.55546L2.88901 5.55546L3.38901 5.55546ZM14.4446 2.44434H14.9446V1.94434L14.4446 1.94434L14.4446 2.44434ZM14.4446 5.55545L14.4446 6.05545L14.9446 6.05545V5.55545H14.4446ZM1.55566 5.55546L1.05566 5.55545L1.05566 6.05546L1.55566 6.05546L1.55566 5.55546ZM1.5557 2.44436L1.5557 1.94436L1.05571 1.94436L1.0557 2.44435L1.5557 2.44436ZM13.1112 5.55546H12.6112V13.5555H13.1112H13.6112V5.55546H13.1112ZM2.889 13.5555H3.389L3.38901 5.55546L2.88901 5.55546L2.38901 5.55546L2.389 13.5555H2.889ZM14.4446 2.44434H13.9446V5.55545H14.4446H14.9446V2.44434H14.4446ZM1.55566 5.55546L2.05566 5.55547L2.0557 2.44436L1.5557 2.44436L1.0557 2.44435L1.05566 5.55545L1.55566 5.55546ZM6.22234 8.22213V8.72213H9.7779V8.22213V7.72213H6.22234V8.22213ZM13.1112 13.5555V13.0555H2.889V13.5555V14.0555H13.1112V13.5555ZM1.5557 2.44436L1.5557 2.94436L14.4446 2.94434L14.4446 2.44434L14.4446 1.94434L1.5557 1.94436L1.5557 2.44436ZM14.4446 5.55545L14.4446 5.05545L1.55566 5.05546L1.55566 5.55546L1.55566 6.05546L14.4446 6.05545L14.4446 5.55545Z" fill="currentColor"/>`,