From e7177a876411b5cc4e498cab3d998a51b6badc47 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Sat, 22 Aug 2026 13:22:22 +1000 Subject: [PATCH] fix(desktop): align web search results presentation with figma (#44039) --- .../session-ui/src/components/basic-tool.css | 127 ++++++++++-------- .../src/components/basic-tool.stories.tsx | 107 +++++++++++++++ .../src/components/message-part.css | 62 ++++++--- .../src/storybook/current-session-fixtures.ts | 25 +++- .../session-ui/src/tools/tool-renderer.tsx | 38 +++++- packages/ui/src/components/collapsible.tsx | 2 +- packages/ui/src/i18n/en.ts | 2 + packages/ui/src/icons/icon/icon.tsx | 4 + 8 files changed, 284 insertions(+), 83 deletions(-) diff --git a/packages/session-ui/src/components/basic-tool.css b/packages/session-ui/src/components/basic-tool.css index b10cc738a5..410648c5ae 100644 --- a/packages/session-ui/src/components/basic-tool.css +++ b/packages/session-ui/src/components/basic-tool.css @@ -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; + } + } +} diff --git a/packages/session-ui/src/components/basic-tool.stories.tsx b/packages/session-ui/src/components/basic-tool.stories.tsx index 6244f3a329..1b799bbe38 100644 --- a/packages/session-ui/src/components/basic-tool.stories.tsx +++ b/packages/session-ui/src/components/basic-tool.stories.tsx @@ -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: () => ( + +
+ Webfetch + + https://www.figma.com + + +
+ + } + /> + ), +} + +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 ( + +
+
+
+
+
+ + {(url, index) => ( + { + 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} + + + )} + + 0}> + + +
+
+ + ) + }, +} diff --git a/packages/session-ui/src/components/message-part.css b/packages/session-ui/src/components/message-part.css index 5617f637e6..972161af8c 100644 --- a/packages/session-ui/src/components/message-part.css +++ b/packages/session-ui/src/components/message-part.css @@ -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; } } diff --git a/packages/session-ui/src/storybook/current-session-fixtures.ts b/packages/session-ui/src/storybook/current-session-fixtures.ts index 5fb7d6a642..10cdadccec 100644 --- a/packages/session-ui/src/storybook/current-session-fixtures.ts +++ b/packages/session-ui/src/storybook/current-session-fixtures.ts @@ -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.", }), ], diff --git a/packages/session-ui/src/tools/tool-renderer.tsx b/packages/session-ui/src/tools/tool-renderer.tsx index 824c7b48fe..68cef52994 100644 --- a/packages/session-ui/src/tools/tool-renderer.tsx +++ b/packages/session-ui/src/tools/tool-renderer.tsx @@ -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 ( 0}>
+ @@ -898,7 +928,7 @@ ToolRegistry.register({ ) { return (
- +
) diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index 4f8ad2863b..ae10ca6ce9 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -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", diff --git a/packages/ui/src/icons/icon/icon.tsx b/packages/ui/src/icons/icon/icon.tsx index 7ccb251420..2de269e61b 100644 --- a/packages/ui/src/icons/icon/icon.tsx +++ b/packages/ui/src/icons/icon/icon.tsx @@ -160,6 +160,10 @@ const icons = { viewBox: "0 0 20 20", body: ``, }, + "fill-triangle-down": { + viewBox: "0 0 16 16", + body: ``, + }, archive: { viewBox: "0 0 16 16", body: ``,