fix(code-mode): preserve nested disclosure identity during progress

Use native append-order call positions as stable Solid Index identities so updates to other calls preserve disclosure state and keyboard focus. Cover status updates and appended calls through the real event dispatcher. Complete Turkish translations alongside the existing locales.

Addresses the independent gatekeeper P2 in PR #789. UI typecheck and all four tool-image/Code Mode browser scenarios pass.
This commit is contained in:
Pascal André 2026-09-26 17:56:25 +02:00
parent 4c7e4a0835
commit aaee7d5ae8
No known key found for this signature in database
4 changed files with 22 additions and 6 deletions

View file

@ -1,4 +1,4 @@
import { For, Show, createMemo } from "solid-js"
import { Index, Show, createMemo } from "solid-js"
import type { ToolRenderer } from "../types"
import { defaultRenderer } from "./default"
import { executeData, executeSummary } from "./execute-data"
@ -31,13 +31,13 @@ export const executeRenderer: ToolRenderer = {
</section>}</Show>
<Show when={data().calls.length}>
<section aria-label={context.t("toolCall.execute.calls")}>
<ol class="execute-calls"><For each={data().calls.slice(0, 200)}>{(call, index) =>
<ol class="execute-calls"><Index each={data().calls.slice(0, 200)}>{(call, index) =>
<li><details><summary>
<span class="execute-call-name">{call.tool}</span>
<span class="execute-call-status" data-status={call.status}>{context.t(`toolCall.status.${call.status}`)}</span>
</summary><Show when={call.input}>{input => markdown(formatUnknownForRender(input())?.text ?? "", "json", `call-${index()}`)}</Show>
<span class="execute-call-name">{call().tool}</span>
<span class="execute-call-status" data-status={call().status}>{context.t(`toolCall.status.${call().status}`)}</span>
</summary><Show when={call().input}>{input => markdown(formatUnknownForRender(input())?.text ?? "", "json", `call-${index}`)}</Show>
</details></li>
}</For></ol>
}</Index></ol>
<Show when={data().calls.length > 200}><p>{context.t("toolCall.execute.more", { count: data().calls.length - 200 })}</p></Show>
</section>
</Show>

View file

@ -1,4 +1,8 @@
export const toolCallMessages = {
"toolCall.execute.script": "Code Mode",
"toolCall.execute.calls": "İç içe araç çağrıları",
"toolCall.execute.more": "{count} ek çağrı gizlendi.",
"toolCall.execute.truncated": "Yerel çıktı kısaltıldı. Tam çıktı:",
"toolCall.pending.waitingToRun": "Çalıştırılmayı bekliyor...",
"toolCall.error.label": "Hata:",
"toolCall.header.copyTitle": "Araç çağrısı başlığını kopyala",

View file

@ -88,6 +88,11 @@ render(() => <ConfigProvider><I18nProvider><ThemeProvider>
{ tool: "paint", status: phase === "running" ? "running" : "completed", input: { prompt: "sky" } },
{ tool: "fail", status: phase === "running" ? "running" : "error", input: { test: true } },
], ...(phase === "running" ? {} : { error: true, truncated: true, outputPath: "/fixture/output.txt" }) }
if (phase === "progress") {
metadata.toolCalls.push({ tool: "third", status: "running", input: { test: true } })
emit("session.tool.progress", { id: "tool", metadata })
return
}
if (phase === "running") {
emit("session.step.started", { agent: "build", model, started: 1 })
emit("session.tool.input.started", { id: "tool", name: "execute" })

View file

@ -93,6 +93,13 @@ test("Code Mode renders native progress, nested errors, script, images and histo
await page.evaluate(() => (window as any).fixture.execute("running"))
await page.locator(".execute-calls").waitFor()
assert.equal(await page.locator('.execute-call-status[data-status="running"]').count(), 2)
const firstCall = page.locator(".execute-calls summary").first()
await firstCall.click()
await firstCall.focus()
await page.evaluate(() => (window as any).fixture.execute("progress"))
assert.equal(await page.locator(".execute-calls li").count(), 3)
assert.equal(await page.locator(".execute-calls details").first().getAttribute("open"), "")
assert.equal(await firstCall.evaluate(el => el === document.activeElement), true)
await page.evaluate(() => (window as any).fixture.execute("complete"))
await decodedImages(page, 2)
await page.locator('.execute-call-status[data-status="completed"]').waitFor()