fix(client): preserve tool detail navigation and layout

This commit is contained in:
_Kerman committed 2026-09-17 12:04:19 +08:00
1 parent f32c0cc3d9
commit 83b2ff7aa1
10 files changed
+81 -23

No files matched your search

+18
View File
@@ -72,6 +72,24 @@ describe.skipIf(MODE === 'record')('web e2e: compact Tool details', () => {
await page.setViewportSize({ width: 360, height: 800 })
const card = page.locator('[data-tool="schedule_create"]')
expect(await card.evaluate(element => element.scrollWidth <= element.clientWidth)).toBe(true)
const reminders = page.locator('[data-tool="schedule_list"]')
await reminders.scrollIntoViewIfNeeded()
const list = reminders.getByRole('list').first()
await list.evaluate((element) => {
const second = element.children[1]!
element.scrollTop += second.getBoundingClientRect().top - element.getBoundingClientRect().top
})
expect(await list.evaluate(element => element.scrollTop)).toBeGreaterThan(0)
const geometry = await reminders.evaluate((element) => {
const title = element.querySelector('ul')!.children[1]!.querySelector('[class*="heading"] > span')!.getBoundingClientRect()
const inspect = element.querySelector('[class*="inspectButton"]')!.getBoundingClientRect()
return {
verticalOverlap: Math.min(title.bottom, inspect.bottom) - Math.max(title.top, inspect.top),
horizontalGap: inspect.left - title.right,
}
})
expect(geometry.verticalOverlap).toBeGreaterThan(0)
expect(geometry.horizontalGap).toBeGreaterThanOrEqual(0)
expect(tripwire.pageErrors).toEqual([])
expect(tripwire.warnings).toEqual([])
}, 60_000)
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/ui-tool/README.md
README.md: 0e41c1f1e0d8e0fb06f83cf7f7d2798b3cf8c1a0
README.zh.md: 6a71cbd11a003b794e5ce79bc2fc63294b6c3f42
README.md: cb01789c11e430a36081fd6e3f8629250038053a
README.zh.md: d648a93e294d6ef7830ecdf895493d44d292c2c6
+1 -1
View File
@@ -68,7 +68,7 @@ Chat diff cards keep nine rows before folding, enough for a file header, one rem
An Auto denial takes precedence over keyed specialized views. Its generic row preserves the call identity, omits raw arguments, and normalizes the stored reason only for display: trim surrounding whitespace and collapse line separators to spaces, with localized fallback for an empty result. Session and SDK error details keep the original reason.
The Details view covers the 36 registered tools: goal and schedule tools, Cordis inspection, workflow and Ralph reports, Session event/search/trace queries, agent and teammate controls, background jobs, persistent terminals, and LSP navigation. These expanded bodies read successful logged results, preserve generic input/output for failures or unsupported data, and keep Inspect available. Dates include the viewer's time zone, and statuses reflect the call result rather than current session state. The browser adapter consumes recorded producer text and JSON; Host service objects and presenter callbacks do not cross into the Client. [Compact tool details](../../../.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.md) records the presentation trade-offs.
Recorded tool details cover goal and schedule tools, Cordis inspection, workflow and Ralph reports, Session event/search/trace queries, agent and teammate controls, background jobs, persistent terminals, and LSP navigation. These expanded bodies read successful logged results, preserve generic input/output for failures or unsupported data, and keep Inspect available. Dates include the viewer's time zone, and statuses reflect the call result rather than current session state. Session traces preserve descendant indentation. LSP results open filesystem paths through the Host callback and display other URIs as text. The browser adapter consumes recorded producer text and JSON; Host service objects and presenter callbacks do not cross into the Client. [Compact tool details](../../../.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.md) records the presentation trade-offs.
Expanded status dots and labels use static semantic colors. Receipt and job-output headers keep neutral text and omit the status while expanded. An interruption receipt confirms only that interruption was requested.
+1 -1
View File
@@ -68,7 +68,7 @@ Chat diff 卡片在折叠前保留九行,足以容纳文件标题、一对删
Auto 拒绝优先于按工具名选择的专门视图。其通用行保留调用身份、省略原始参数,并且只在显示时归一化存储的理由:去除首尾空白,把行分隔符折叠为空格,结果为空时使用本地化通用理由。Session 与 SDK 错误详情保留原始理由。
Details 视图覆盖已注册的 36 个工具:目标和定时任务工具、Cordis 检查、workflow 与 Ralph 报告、Session 事件/搜索/轨迹查询、Agent 与 teammate 控制、后台作业、持久终端以及 LSP 导航。展开内容读取成功的记录结果,为失败或不支持的数据保留通用输入/输出,并保留 Inspect。日期包含查看者的时区,状态反映调用结果而非当前会话状态。浏览器适配器消费已记录的 producer 文本与 JSON;Host service 对象和 presenter 回调不会进入 Client。[紧凑工具详情](../../../.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.zh.md)记录了呈现取舍。
记录结果的工具详情覆盖目标和定时任务工具、Cordis 检查、workflow 与 Ralph 报告、Session 事件/搜索/轨迹查询、Agent 与 teammate 控制、后台作业、持久终端以及 LSP 导航。展开内容读取成功的记录结果,为失败或不支持的数据保留通用输入/输出,并保留 Inspect。日期包含查看者的时区,状态反映调用结果而非当前会话状态。Session 轨迹保留后代的缩进。LSP 结果通过 Host 回调打开文件系统路径,其他 URI 则显示为文本。浏览器适配器消费已记录的 producer 文本与 JSON;Host service 对象和 presenter 回调不会进入 Client。[紧凑工具详情](../../../.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.zh.md)记录了呈现取舍。
展开后的状态圆点和文字使用静态语义色。操作回执和任务输出的标题保持中性色,展开时省略标题中的状态。中断回执仅确认已发出中断请求。
@@ -26,7 +26,7 @@
border-top: 0.5px solid var(--dsw-alias-border-l2);
}
.root[data-inspect]:not([data-caption]) > .list > .item:first-child,
.root[data-inspect]:not([data-caption]) > .list > .item,
.root[data-inspect]:not([data-caption]) .empty,
.root[data-inspect] .caption {
padding-inline-end: 88px;
@@ -87,9 +87,10 @@ function lspDetails(args: Record<string, unknown>, text: string, t: DetailTransl
if (match === null) return null
const [, path, row, column] = match
if (path === undefined || row === undefined || column === undefined) return null
const isUri = /^[a-z][a-z\d+.-]*:/iu.test(path) && !/^[a-z]:[\\/]/iu.test(path)
items.push({
title: path, subtitle: t('detail.location', { line: row, column }), fields: [],
.../^[a-z][a-z\d+.-]*:\/\//iu.test(path) ? {} : { location: { path, line: Number(row) } },
...isUri ? {} : { location: { path, line: Number(row) } },
})
}
const count = items.filter(item => item.title !== undefined).length
@@ -144,7 +144,7 @@ function traceDetails(name: string, text: string, t: DetailTranslate, locale: st
const label = section.startsWith('Ancestors (nearest first):') ? t('detail.trace.ancestors') : section.startsWith('Descendants:') ? t('detail.trace.descendants') : undefined
if (label === undefined || firstBreak < 0) return null
const body = section.slice(firstBreak + 1)
items.push({ title: label, fields: [], ...(body === '- none' || body === '- none (target is a root session)' ? { description: t('detail.none') } : { lines: body.split('\n').map(line => line.replace(/^\s*- /u, '')) }) })
items.push({ title: label, fields: [], ...(body === '- none' || body === '- none (target is a root session)' ? { description: t('detail.none') } : { lines: body.split('\n').map(line => line.replace(/^(\s*)- /u, '$1')) }) })
}
return detailList(items, match[2], t)
}
@@ -63,7 +63,10 @@ describe('control detail adapters', () => {
{ sessionId: 'pty-2' as never, name: 'done', type: 'shell', pid: 4, status: { kind: 'exited' as const, exitCode: 2, signal: null } },
]
expect(details('terminal_list', renderList(terminals, 1000))?.items).toHaveLength(2)
expect(details('lsp', 'src/a.ts:2:3\nhttps://example.test/a.ts:4:5', { file_path: 'src/main.ts', line: 2, character: 3, operation: 'findReferences' })?.items).toHaveLength(2)
const locations = details('lsp', 'src/a.ts:2:3\nhttps://example.test/a.ts:4:5\nuntitled:Untitled-1:6:7\nC:\\src\\a.ts:8:9\nD:/lib/a.ts:10:11', { file_path: 'src/main.ts', line: 2, character: 3, operation: 'findReferences' })?.items
expect(locations?.map(item => item.location)).toEqual([
{ path: 'src/a.ts', line: 2 }, undefined, undefined, { path: 'C:\\src\\a.ts', line: 8 }, { path: 'D:/lib/a.ts', line: 10 },
])
expect(details('lsp', 'No results.', { file_path: 'src/main.ts', line: 2, character: 3, operation: 'findReferences' })?.empty).toBe('No results')
expect(details('lsp', 'hover text', { file_path: 'src/main.ts', line: 2, character: 3, operation: 'hover' })?.items[0]?.markdown).toBe('hover text')
})
@@ -122,21 +125,30 @@ describe('inspection detail adapters', () => {
it('uses the session-query producer presentation for searches, reads, and traces', () => {
const session = sessionRecord('s1', true, true)
const hit = { ...session, bestMatch: { sessionId: 's1' as never, seq: 2, type: 'user/message', time: 2_000, surface: 'current', snippet: 'needle' } }
const titles = new Map([['s1' as never, { title: 'Session one' } as never]]) as never
const title = { text: 'Session one' }
const titles = new Map([
['s1' as never, title], ['s2' as never, { text: 'Child' }], ['s3' as never, { text: 'Grandchild' }],
]) as Parameters<typeof presentation.formatSessionSearch>[1]
const sessionSearch = presentation.formatSessionSearch({ items: [hit as never], capped: true }, titles, new Set())
expect(details('session_search', sessionSearch)?.items[0]?.description).toBe('needle')
expect(details('session_search', sessionSearch)?.items[0]).toMatchObject({ title: 'Session one', subtitle: 's1', description: 'needle' })
const eventSearch = presentation.formatEventSearch(
's1' as never, { title: 'Session one' } as never,
's1' as never, title,
{ items: [{ sessionId: 's1' as never, seq: 2 as never, type: 'tool/call', time: 2_000, surface: 'current', snippet: 'needle' }], capped: false },
)
expect(details('session_event_search', eventSearch)?.items[0]?.title).toBe('needle')
const trace = presentation.formatSessionTrace(
{ target: session as never, ancestors: [], descendants: [], complete: true, root: session as never } as never,
[], false, [], titles,
[], false, [{ record: sessionRecord('s2', true, true) as never, descendants: [{ record: sessionRecord('s3', true, true) as never, descendants: [] }] }], titles,
)
expect(details('session_trace', trace)?.items).toHaveLength(3)
const traceItems = details('session_trace', trace)?.items
expect(traceItems).toHaveLength(3)
expect(traceItems?.[0]).toMatchObject({ title: 'Session one', subtitle: 's1' })
expect(traceItems?.[2]?.lines).toEqual([
's2 — Child | 1970-01-01T00:00:01.000Z | live, persisted',
' s3 — Grandchild | 1970-01-01T00:00:01.000Z | live, persisted',
])
const eventTrace = presentation.formatEventTrace(
's1' as never, { title: 'Session one' } as never,
's1' as never, title,
{
session: { id: 's1' as never, createdAt: 1_000 } as never,
target: { sessionId: 's1' as never, seq: 2 as never, type: 'tool/call', time: 2_000, surface: 'current' },
@@ -146,7 +158,7 @@ describe('inspection detail adapters', () => {
expect(details('session_event_trace', eventTrace)?.items[0]?.fields).toContainEqual({ label: 'Target event', value: 'seq 2 | tool/call | current | 1970-01-01T00:00:02.000Z' })
const event = { seq: 2, time: 2_000, type: 'tool/call', data: { name: 'x' } }
const eventRead = presentation.formatEventRead(
's1' as never, { title: 'Session one' } as never,
's1' as never, title,
{
session: {} as never, inheritedEventCount: 0 as never, target: event as never,
events: [event as never], startSeq: 2 as never, endSeq: 2 as never,
+3 -3
View File
@@ -16,7 +16,7 @@
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"details-call-5","name":"update_goal","arguments":"{\"goal_id\":\"{{id:1}}\",\"action\":\"complete\"}"}}
{"type":"tool/result","surfaceOp":"append","data":{"turn":1,"step":1,"message":{"role":"user","content":[{"type":"tool-result","toolCallId":"details-call-5","content":[{"type":"text","text":"{\"goal\":{\"id\":\"{{id:1}}\",\"revision\":2,\"objective\":\"Ship compact tool cards\",\"phase\":\"complete\",\"roundsStarted\":0,\"maxGoalRounds\":8},\"activation\":\"disarmed\"}"}],"isError":false}],"source":{"kind":"tool","callId":"details-call-5"},"id":"{{message:7}}"}}}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"details-call-6","name":"schedule_list","arguments":"{}"}}
{"type":"tool/result","surfaceOp":"append","data":{"turn":1,"step":1,"message":{"role":"user","content":[{"type":"tool-result","toolCallId":"details-call-6","content":[{"type":"text","text":"[{\"id\":\"details-reminder\",\"prompt\":\"Review the build and open the demo\",\"kind\":\"every\",\"everySeconds\":3600,\"scheduledAt\":\"2099-09-10T09:00:00.000Z\",\"state\":\"scheduled\",\"deliveryMode\":\"session-local\"}]"}],"isError":false}],"source":{"kind":"tool","callId":"details-call-6"},"id":"{{message:8}}"}}}
{"type":"tool/result","surfaceOp":"append","data":{"turn":1,"step":1,"message":{"role":"user","content":[{"type":"tool-result","toolCallId":"details-call-6","content":[{"type":"text","text":"[{\"id\":\"details-reminder\",\"prompt\":\"Review the build and open the demo\",\"kind\":\"every\",\"everySeconds\":3600,\"scheduledAt\":\"2099-09-10T09:00:00.000Z\",\"state\":\"scheduled\",\"deliveryMode\":\"session-local\"},{\"id\":\"details-reminder-2\",\"prompt\":\"Review the narrow reminder layout before sharing the updated demo with the team\",\"kind\":\"every\",\"everySeconds\":3600,\"scheduledAt\":\"2099-09-10T09:00:00.000Z\",\"state\":\"scheduled\",\"deliveryMode\":\"session-local\"},{\"id\":\"details-reminder-3\",\"prompt\":\"Check the recorded tool results and finish the remaining review comments\",\"kind\":\"every\",\"everySeconds\":3600,\"scheduledAt\":\"2099-09-10T09:00:00.000Z\",\"state\":\"scheduled\",\"deliveryMode\":\"session-local\"},{\"id\":\"details-reminder-4\",\"prompt\":\"Publish the reviewed demo and verify the deployment status\",\"kind\":\"every\",\"everySeconds\":3600,\"scheduledAt\":\"2099-09-10T09:00:00.000Z\",\"state\":\"scheduled\",\"deliveryMode\":\"session-local\"}]"}],"isError":false}],"source":{"kind":"tool","callId":"details-call-6"},"id":"{{message:8}}"}}}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"details-call-7","name":"schedule_delete","arguments":"{\"id\":\"details-reminder\"}"}}
{"type":"tool/result","surfaceOp":"append","data":{"turn":1,"step":1,"message":{"role":"user","content":[{"type":"tool-result","toolCallId":"details-call-7","content":[{"type":"text","text":"{\"id\":\"details-reminder\",\"deleted\":true}"}],"isError":false}],"source":{"kind":"tool","callId":"details-call-7"},"id":"{{message:9}}"}}}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"details-call-8","name":"todo_write","arguments":"{\"todos\":[{\"content\":\"Keep the existing row and expansion controls\",\"status\":\"completed\"},{\"content\":\"Render compact fields and lists\",\"status\":\"completed\"},{\"content\":\"Publish the reviewed cards\",\"status\":\"pending\"}]}"}}
@@ -41,7 +41,7 @@
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"details-call-17","name":"session_search","arguments":"{\"query\":\"工具卡片\",\"limit\":5}"}}
{"type":"tool/result","surfaceOp":"append","data":{"turn":1,"step":1,"message":{"role":"user","content":[{"type":"tool-result","toolCallId":"details-call-17","content":[{"type":"text","text":"Session search results (2):\n\n1. Session todo-diff-demo — 任务清单变化示例\n Created: 2026-09-10T08:00:00.000Z\n Parent: root\n Availability: persisted\n Best match: seq 1 | user/message | current | 2026-09-10T08:00:00.000Z\n Snippet: 重新设计任务清单工具卡片。\n\n2. Session tool-details-demo — Tool card details demo\n Created: 2026-09-10T07:00:00.000Z\n Parent: root\n Availability: persisted\n Best match: seq 1 | user/message | current | 2026-09-10T07:00:00.000Z\n Snippet: Show a goal, a to-do list, and reminders."}],"isError":false}],"source":{"kind":"tool","callId":"details-call-17"},"id":"{{message:19}}"}}}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"details-call-18","name":"session_trace","arguments":"{\"session_id\":\"todo-diff-demo\"}"}}
{"type":"tool/result","surfaceOp":"append","data":{"turn":1,"step":1,"message":{"role":"user","content":[{"type":"tool-result","toolCallId":"details-call-18","content":[{"type":"text","text":"Session todo-diff-demo — 任务清单变化示例\nCreated: 2026-09-10T08:00:00.000Z\nAvailability: persisted\n\nAncestors (nearest first):\n- none (target is a root session)\n\nDescendants:\n- none"}],"isError":false}],"source":{"kind":"tool","callId":"details-call-18"},"id":"{{message:20}}"}}}
{"type":"tool/result","surfaceOp":"append","data":{"turn":1,"step":1,"message":{"role":"user","content":[{"type":"tool-result","toolCallId":"details-call-18","content":[{"type":"text","text":"Session todo-diff-demo — 任务清单变化示例\nCreated: 2026-09-10T08:00:00.000Z\nAvailability: persisted\n\nAncestors (nearest first):\n- none (target is a root session)\n\nDescendants:\n- review-child — Layout review | 2026-09-10T08:05:00.000Z | persisted\n - review-grandchild — Narrow layout | 2026-09-10T08:10:00.000Z | persisted"}],"isError":false}],"source":{"kind":"tool","callId":"details-call-18"},"id":"{{message:20}}"}}}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"details-call-19","name":"list_subagent_models","arguments":"{\"provider\":\"deepseek\"}"}}
{"type":"tool/result","surfaceOp":"append","data":{"turn":1,"step":1,"message":{"role":"user","content":[{"type":"tool-result","toolCallId":"details-call-19","content":[{"type":"text","text":"deepseek/deepseek-chat — DeepSeek Chat\ndeepseek/deepseek-reasoner — DeepSeek Reasoner"}],"isError":false}],"source":{"kind":"tool","callId":"details-call-19"},"id":"{{message:21}}"}}}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"details-call-20","name":"subagent","arguments":"{\"prompt\":\"检查工具卡片布局,并列出三个最值得改进的地方。\"}"}}
@@ -71,7 +71,7 @@
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"details-call-32","name":"lsp","arguments":"{\"operation\":\"goToDefinition\",\"file_path\":\"src/app.ts\",\"line\":18,\"character\":5}"}}
{"type":"tool/result","surfaceOp":"append","data":{"turn":1,"step":1,"message":{"role":"user","content":[{"type":"tool-result","toolCallId":"details-call-32","content":[{"type":"text","text":"src/tool-details.ts:24:1"}],"isError":false}],"source":{"kind":"tool","callId":"details-call-32"},"id":"{{message:34}}"}}}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"details-call-33","name":"lsp","arguments":"{\"operation\":\"findReferences\",\"file_path\":\"src/tool-details.ts\",\"line\":24,\"character\":1}"}}
{"type":"tool/result","surfaceOp":"append","data":{"turn":1,"step":1,"message":{"role":"user","content":[{"type":"tool-result","toolCallId":"details-call-33","content":[{"type":"text","text":"src/app.ts:18:5\nsrc/tool-row.tsx:42:9\nsrc/todo-row.tsx:67:12"}],"isError":false}],"source":{"kind":"tool","callId":"details-call-33"},"id":"{{message:35}}"}}}
{"type":"tool/result","surfaceOp":"append","data":{"turn":1,"step":1,"message":{"role":"user","content":[{"type":"tool-result","toolCallId":"details-call-33","content":[{"type":"text","text":"src/app.ts:18:5\nsrc/tool-row.tsx:42:9\nsrc/todo-row.tsx:67:12\nuntitled:Untitled-1:6:7"}],"isError":false}],"source":{"kind":"tool","callId":"details-call-33"},"id":"{{message:35}}"}}}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"details-call-34","name":"lsp","arguments":"{\"operation\":\"goToImplementation\",\"file_path\":\"src/types.ts\",\"line\":12,\"character\":18}"}}
{"type":"tool/result","surfaceOp":"append","data":{"turn":1,"step":1,"message":{"role":"user","content":[{"type":"tool-result","toolCallId":"details-call-34","content":[{"type":"text","text":"src/tool-details.ts:24:1\nsrc/fallback-details.ts:8:1"}],"isError":false}],"source":{"kind":"tool","callId":"details-call-34"},"id":"{{message:36}}"}}}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"details-call-35","name":"lsp","arguments":"{\"operation\":\"hover\",\"file_path\":\"src/tool-details.ts\",\"line\":24,\"character\":1}"}}
+32 -5
View File
@@ -59,9 +59,9 @@
- term: Rounds
- definition: 0 / 8
- button "Inspect"
- button "List reminders 1 reminders" [expanded]:
- button "List reminders 4 reminders" [expanded]:
- img
- text: List reminders 1 reminders
- text: List reminders 4 reminders
- list:
- listitem:
- text: Review the build and open the demo
@@ -71,6 +71,30 @@
- definition: Every 1 h
- term: Status
- definition: Scheduled
- listitem:
- text: Review the narrow reminder layout before sharing the updated demo with the team
- term: Scheduled for
- definition: Sep 10, 2099, {{clock}} GMT+8
- term: Repeat
- definition: Every 1 h
- term: Status
- definition: Scheduled
- listitem:
- text: Check the recorded tool results and finish the remaining review comments
- term: Scheduled for
- definition: Sep 10, 2099, {{clock}} GMT+8
- term: Repeat
- definition: Every 1 h
- term: Status
- definition: Scheduled
- listitem:
- text: Publish the reviewed demo and verify the deployment status
- term: Scheduled for
- definition: Sep 10, 2099, {{clock}} GMT+8
- term: Repeat
- definition: Every 1 h
- term: Status
- definition: Scheduled
- button "Inspect"
- button "Delete reminder details-reminder" [expanded]:
- img
@@ -394,7 +418,9 @@
- paragraph: None
- listitem:
- text: Descendant sessions
- paragraph: None
- list:
- listitem: review-child — Layout review | {{timestamp}} | persisted
- listitem: review-grandchild — Narrow layout | {{timestamp}} | persisted
- button "Inspect"
- button "List models deepseek" [expanded]:
- img
@@ -571,9 +597,9 @@
- button "src/tool-details.ts"
- text: Line 24, column 1
- button "Inspect"
- button "Find references src/tool-details.ts · 3 locations" [expanded]:
- button "Find references src/tool-details.ts · 4 locations" [expanded]:
- img
- text: Find references src/tool-details.ts · 3 locations
- text: Find references src/tool-details.ts · 4 locations
- text: Recorded result
- list:
- listitem:
@@ -585,6 +611,7 @@
- listitem:
- button "src/todo-row.tsx"
- text: Line 67, column 12
- listitem: untitled:Untitled-1 Line 6, column 7
- button "Inspect"
- button "Find implementation src/types.ts · 2 locations" [expanded]:
- img