Implement shared compact split and unified tool-call diff layout (#270)

# PR Title

Implement shared compact split and unified tool-call diff layout

---
Fixes #268 
# PR Description

## Summary

This PR makes tool-call diffs more compact in both `Unified` and `Split`
views by reducing wasted horizontal space in line-number gutters and
content indentation.

## What changed

- introduced a shared compact-diff framework for tool-call diffs
- kept mobile-specific policy limited to:
  - forcing unified mode below the breakpoint
  - enabling wrap only in mobile unified mode
- added mode-specific compact applicators in the diff viewer:
  - unified applicator
  - split applicator
- reduced gutter width waste by measuring rendered line-number text and
tightening column width around it
- removed unnecessary right-side content padding
- aligned `+` / `-` markers closer to the left edge across both views
- simplified cleanup after gatekeeper review by removing extra plumbing
and residue

## Screenshots

### Before

<img width="581" height="341" alt="image"
src="https://github.com/user-attachments/assets/ec47b256-749a-4afc-8879-aaf33f0b46b6"
/>

### After

<img width="470" height="586" alt="image"
src="https://github.com/user-attachments/assets/7258a5a2-47c4-408d-84bc-1b497761c7ad"
/>

## Architectural approach

This change intentionally uses:

- shared policy in
`packages/ui/src/components/tool-call/diff-render.tsx`
- shared helper/measurement logic in
`packages/ui/src/components/diff-viewer.tsx`
- mode-specific applicators where unified and split DOM differ
- CSS for shared visual spacing and alignment cleanup

The goal was to keep the implementation architecturally clean and avoid
building separate duplicated compact-diff features for:

- mobile vs desktop
- unified vs split

Instead, the feature shares one compact-diff concept and only diverges
where the upstream diff DOM requires separate handling.

## Files changed

- `packages/ui/src/components/tool-call/diff-render.tsx`
- `packages/ui/src/components/diff-viewer.tsx`
- `packages/ui/src/styles/messaging/tool-call.css`
- `packages/ui/src/types/message.ts`

## Validation

Manual validation was performed in the running UI.

Verified manually:

- compact unified gutters on mobile
- compact unified gutters on desktop
- compact split gutters on desktop
- tighter operator alignment in both modes

Also verified:

- `npm run typecheck` passes

## Notes

- This PR is intended to address the compact diff layout problem
described in the related issue.
- Diff-specific CSS still lives in `tool-call.css`; future extraction
into a smaller dedicated stylesheet is possible but not required for
this change.

---------

Co-authored-by: Shantur Rathore <i@shantur.com>
This commit is contained in:
VooDisss
2026-04-02 01:13:32 +03:00
committed by GitHub
parent df16b64a95
commit 4f236ce36f
11 changed files with 384 additions and 35 deletions

View File

@@ -1,4 +1,4 @@
import { createMemo, Show, createEffect, onCleanup } from "solid-js"
import { createMemo, Show, createEffect } from "solid-js"
import { DiffView, DiffModeEnum } from "@git-diff-view/solid"
import "@git-diff-view/solid/styles/diff-view-pure.css"
import { disableCache } from "@git-diff-view/core"
@@ -20,6 +20,7 @@ interface ToolCallDiffViewerProps {
filePath?: string
theme: "light" | "dark"
mode: DiffViewMode
wrap?: boolean
onRendered?: () => void
cachedHtml?: string
cacheEntryParams?: CacheEntryParams
@@ -31,11 +32,183 @@ type DiffData = {
hunks: string[]
}
type CaptureContext = {
theme: ToolCallDiffViewerProps["theme"]
mode: DiffViewMode
diffText: string
cacheEntryParams?: CacheEntryParams
function measureTextWidth(container: HTMLElement, text: string, source: HTMLElement) {
const computed = window.getComputedStyle(source)
const probe = document.createElement("span")
probe.textContent = text || ""
probe.style.position = "absolute"
probe.style.visibility = "hidden"
probe.style.pointerEvents = "none"
probe.style.display = "inline-block"
probe.style.width = "auto"
probe.style.maxWidth = "none"
probe.style.whiteSpace = "nowrap"
probe.style.fontFamily = computed.fontFamily
probe.style.fontSize = computed.fontSize
probe.style.fontWeight = computed.fontWeight
probe.style.fontStyle = computed.fontStyle
probe.style.letterSpacing = computed.letterSpacing
probe.style.fontVariant = computed.fontVariant
probe.style.textTransform = computed.textTransform
probe.style.lineHeight = computed.lineHeight
container.appendChild(probe)
const width = Math.ceil(probe.getBoundingClientRect().width)
probe.remove()
return width
}
function computeCompactWidth(
container: HTMLElement,
entries: Array<{ text: string; source: HTMLElement }>,
maxWidthPx = 40,
) {
const measuredLabelWidthPx = entries.reduce((max, entry) => {
return Math.max(max, measureTextWidth(container, entry.text, entry.source))
}, 0)
const fallbackTextLength = entries.reduce((max, entry) => Math.max(max, entry.text.length), 1)
const fallbackWidthPx = Math.round(fallbackTextLength * 7 + 4)
return Math.max(2, Math.min(maxWidthPx, measuredLabelWidthPx > 0 ? measuredLabelWidthPx + 2 : fallbackWidthPx))
}
function applyCompactUnifiedGutter(container: HTMLElement, wrap: boolean) {
const tableWrapper = container.querySelector<HTMLElement>(".unified-diff-table-wrapper")
const table = container.querySelector<HTMLTableElement>(".unified-diff-table")
const numberCol = container.querySelector<HTMLTableColElement>(".unified-diff-table-num-col")
const gutterRows = container.querySelectorAll<HTMLElement>(".diff-line-num")
const hunkGutters = container.querySelectorAll<HTMLElement>(".diff-line-hunk-action, .diff-line-widget-wrapper, .diff-line-extend-wrapper")
const entries: Array<{ gutter: HTMLElement; label: HTMLElement; text: string }> = []
if (table) {
if (wrap) {
table.classList.add("table-fixed")
table.style.tableLayout = "fixed"
table.style.width = "100%"
table.style.minWidth = "100%"
} else {
table.classList.remove("table-fixed")
table.style.tableLayout = "auto"
table.style.width = "max-content"
table.style.minWidth = "100%"
}
}
gutterRows.forEach((gutter) => {
const oldSpan = gutter.querySelector<HTMLElement>("[data-line-old-num]")
const newSpan = gutter.querySelector<HTMLElement>("[data-line-new-num]")
const spacer = gutter.querySelector<HTMLElement>(".shrink-0")
const flexWrapper = gutter.querySelector<HTMLElement>(":scope > .flex")
const currentLabel = gutter.querySelector<HTMLElement>(":scope > .tool-call-diff-compact-line-number")
const oldText = oldSpan?.textContent?.trim() ?? ""
const newText = newSpan?.textContent?.trim() ?? ""
const hasUsableNew = newText.length > 0 && newText !== "0"
const hasUsableOld = oldText.length > 0 && oldText !== "0"
const visibleText = hasUsableNew ? newText : hasUsableOld ? oldText : newText || oldText
if (flexWrapper) flexWrapper.style.display = "none"
if (spacer) spacer.style.display = "none"
if (oldSpan) { oldSpan.style.display = "none"; oldSpan.style.width = "auto" }
if (newSpan) { newSpan.style.display = "none"; newSpan.style.width = "auto" }
gutter.style.paddingLeft = "1px"
gutter.style.paddingRight = "1px"
gutter.style.textAlign = "left"
const label = currentLabel ?? document.createElement("span")
label.className = "tool-call-diff-compact-line-number"
label.textContent = visibleText
label.setAttribute("aria-hidden", visibleText ? "false" : "true")
if (!currentLabel) gutter.appendChild(label)
entries.push({ gutter, label, text: visibleText })
})
const gutterWidthPx = computeCompactWidth(container, entries.map((entry) => ({ text: entry.text, source: entry.label })))
const gutterWidth = `${gutterWidthPx}px`
const compactAsideWidth = `${Math.max(8, gutterWidthPx - 10)}px`
if (tableWrapper) {
tableWrapper.style.setProperty("--diff-aside-width", compactAsideWidth)
tableWrapper.style.setProperty("--diff-aside-width--", compactAsideWidth)
}
if (numberCol) {
numberCol.style.width = gutterWidth
}
entries.forEach(({ gutter, label }) => {
gutter.style.width = gutterWidth
gutter.style.minWidth = gutterWidth
gutter.style.maxWidth = gutterWidth
label.style.width = "auto"
label.style.maxWidth = "none"
})
hunkGutters.forEach((gutter) => {
gutter.style.width = gutterWidth
gutter.style.minWidth = gutterWidth
gutter.style.maxWidth = gutterWidth
gutter.style.paddingLeft = "0"
gutter.style.paddingRight = "0"
})
}
function applyCompactSplitGutter(container: HTMLElement) {
const oldWrapper = container.querySelector<HTMLElement>(".old-diff-table-wrapper")
const newWrapper = container.querySelector<HTMLElement>(".new-diff-table-wrapper")
const numberCells = Array.from(container.querySelectorAll<HTMLElement>(".diff-line-old-num, .diff-line-new-num"))
const hunkActions = Array.from(container.querySelectorAll<HTMLElement>(".diff-line-hunk-action, .diff-line-widget-wrapper, .diff-line-extend-wrapper"))
const numberSpans = numberCells
.map((cell) => ({ cell, span: cell.querySelector<HTMLElement>("[data-line-num]") }))
.filter((entry): entry is { cell: HTMLElement; span: HTMLElement } => Boolean(entry.span))
const gutterWidthPx = computeCompactWidth(
container,
numberSpans.map(({ span }) => ({ text: span.textContent?.trim() ?? "", source: span })),
64,
)
const gutterWidth = `${gutterWidthPx}px`
;[oldWrapper, newWrapper].forEach((wrapper) => {
if (wrapper) {
wrapper.style.setProperty("--diff-aside-width", gutterWidth)
}
})
numberCells.forEach((cell) => {
cell.style.width = gutterWidth
cell.style.minWidth = gutterWidth
cell.style.maxWidth = gutterWidth
cell.style.paddingLeft = "2px"
cell.style.paddingRight = "2px"
cell.style.textAlign = "left"
cell.style.whiteSpace = "nowrap"
cell.style.overflowWrap = "normal"
cell.style.wordBreak = "normal"
})
numberSpans.forEach(({ span }) => {
span.style.whiteSpace = "nowrap"
span.style.overflowWrap = "normal"
span.style.wordBreak = "normal"
})
hunkActions.forEach((cell) => {
cell.style.width = gutterWidth
cell.style.minWidth = gutterWidth
cell.style.maxWidth = gutterWidth
cell.style.paddingLeft = "0"
cell.style.paddingRight = "0"
})
}
function applyCompactDiffLayout(container: HTMLElement, mode: DiffViewMode, wrap = false) {
if (mode === "unified") {
applyCompactUnifiedGutter(container, wrap)
return
}
if (mode === "split") {
applyCompactSplitGutter(container)
}
}
export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
@@ -67,12 +240,15 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
const contextKey = createMemo(() => {
const data = diffData()
if (!data) return ""
return `${props.theme}|${props.mode}|${props.diffText}`
return `${props.theme}|${props.mode}|${props.wrap ? "wrap" : "nowrap"}|${props.diffText}`
})
createEffect(() => {
const cachedHtml = props.cachedHtml
if (cachedHtml) {
if (diffContainerRef) {
applyCompactDiffLayout(diffContainerRef, props.mode, Boolean(props.wrap))
}
// When we are given cached HTML, we rely on the caller's cache
// and simply notify once rendered.
props.onRendered?.()
@@ -83,9 +259,10 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
if (!key) return
if (!diffContainerRef) return
if (lastCapturedKey === key) return
requestAnimationFrame(() => {
if (!diffContainerRef) return
applyCompactDiffLayout(diffContainerRef, props.mode, Boolean(props.wrap))
const markup = diffContainerRef.innerHTML
if (!markup) return
lastCapturedKey = key
@@ -95,6 +272,7 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
html: markup,
theme: props.theme,
mode: props.mode,
wrap: props.wrap,
})
}
props.onRendered?.()
@@ -122,7 +300,7 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
diffViewMode={props.mode === "split" ? DiffModeEnum.Split : DiffModeEnum.Unified}
diffViewTheme={props.theme}
diffViewHighlight
diffViewWrap={false}
diffViewWrap={Boolean(props.wrap)}
diffViewFontSize={13}
/>
</ErrorBoundary>
@@ -131,7 +309,7 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
</div>
}
>
<div innerHTML={props.cachedHtml} />
<div ref={diffContainerRef} innerHTML={props.cachedHtml} />
</Show>
</div>
)

View File

@@ -1,10 +1,13 @@
import { Suspense, lazy, onMount, type Accessor, type JSXElement } from "solid-js"
import { Suspense, createEffect, createMemo, createSignal, lazy, onMount, type Accessor, type JSXElement } from "solid-js"
import type { ToolState } from "@opencode-ai/sdk/v2"
import useMediaQuery from "@suid/material/useMediaQuery"
import { AlignJustify, Copy, Split, WrapText } from "lucide-solid"
import type { RenderCache } from "../../types/message"
import type { DiffViewMode } from "../../stores/preferences"
import type { DiffPayload, DiffRenderOptions, ToolScrollHelpers } from "./types"
import { getRelativePath } from "./utils"
import { getCacheEntry } from "../../lib/global-cache"
import { copyToClipboard } from "../../lib/clipboard"
const LazyToolCallDiffViewer = lazy(() =>
import("../diff-viewer").then((module) => ({ default: module.ToolCallDiffViewer })),
@@ -43,6 +46,16 @@ export function createDiffContentRenderer(params: {
handleScrollRendered: () => void
onContentRendered?: () => void
}) {
const compactDiffQuery = useMediaQuery("(max-width: 640px)")
const [mobileModeOverride, setMobileModeOverride] = createSignal<DiffViewMode | undefined>(undefined)
const [wordWrapEnabled, setWordWrapEnabled] = createSignal(true)
createEffect(() => {
if (!compactDiffQuery()) {
setMobileModeOverride(undefined)
}
})
const registerTracked = (element: HTMLDivElement | null) => {
params.scrollHelpers.registerContainer(element)
}
@@ -58,7 +71,12 @@ export function createDiffContentRenderer(params: {
: params.t("toolCall.diff.label"))
const selectedVariant = options?.variant === "permission-diff" ? "permission-diff" : "diff"
const cacheHandle = selectedVariant === "permission-diff" ? params.permissionDiffCache : params.diffCache
const diffMode = () => (params.preferences().diffViewMode || "split") as DiffViewMode
const preferredMode = () => (params.preferences().diffViewMode || "split") as DiffViewMode
const effectiveMode = () => {
if (!compactDiffQuery()) return preferredMode()
return mobileModeOverride() || "unified"
}
const shouldWrap = () => wordWrapEnabled()
const themeKey = params.isDark() ? "dark" : "light"
const state = params.toolState()
const disableScrollTracking = Boolean(
@@ -76,17 +94,40 @@ export function createDiffContentRenderer(params: {
}
})()
let cachedHtml: string | undefined
const cached = getCacheEntry<RenderCache>(cacheEntryParams)
const currentMode = diffMode()
if (cached && cached.text === payload.diffText && cached.theme === themeKey && cached.mode === currentMode) {
cachedHtml = cached.html
}
const currentMode = createMemo(() => effectiveMode())
const currentWrap = createMemo(() => shouldWrap())
const cachedHtml = createMemo(() => {
const cached = getCacheEntry<RenderCache>(cacheEntryParams)
if (
cached
&& cached.text === payload.diffText
&& cached.theme === themeKey
&& cached.mode === currentMode()
&& cached.wrap === currentWrap()
) {
return cached.html
}
return undefined
})
const handleModeChange = (mode: DiffViewMode) => {
if (compactDiffQuery()) {
setMobileModeOverride(mode)
}
params.setDiffViewMode(mode)
}
const nextViewMode = (): DiffViewMode => (currentMode() === "split" ? "unified" : "split")
const viewModeTitle = () =>
nextViewMode() === "split"
? params.t("toolCall.diff.switchToSplit")
: params.t("toolCall.diff.switchToUnified")
const wordWrapTitle = () =>
wordWrapEnabled()
? params.t("toolCall.diff.disableWordWrap")
: params.t("toolCall.diff.enableWordWrap")
const copyPatchTitle = () => params.t("toolCall.diff.copyPatch")
const handleDiffRendered = () => {
if (!disableScrollTracking) {
params.handleScrollRendered()
@@ -95,41 +136,54 @@ export function createDiffContentRenderer(params: {
}
return (
<div
class="message-text tool-call-markdown tool-call-markdown-large tool-call-diff-shell"
ref={registerRef}
onScroll={disableScrollTracking ? undefined : params.scrollHelpers.handleScroll}
>
<div
class="message-text tool-call-markdown tool-call-markdown-large tool-call-diff-shell"
data-diff-mode={currentMode()}
ref={registerRef}
onScroll={disableScrollTracking ? undefined : params.scrollHelpers.handleScroll}
>
<div class="tool-call-diff-toolbar" role="group" aria-label={params.t("toolCall.diff.viewMode.ariaLabel")}>
<span class="tool-call-diff-toolbar-label">{toolbarLabel}</span>
<div class="tool-call-diff-toggle">
<div class="file-viewer-toolbar">
<button
type="button"
class={`tool-call-diff-mode-button${diffMode() === "split" ? " active" : ""}`}
aria-pressed={diffMode() === "split"}
onClick={() => handleModeChange("split")}
class="file-viewer-toolbar-icon-button"
onClick={() => void copyToClipboard(payload.diffText)}
aria-label={copyPatchTitle()}
title={copyPatchTitle()}
>
{params.t("toolCall.diff.viewMode.split")}
<Copy class="h-4 w-4" aria-hidden="true" />
</button>
<button
type="button"
class={`tool-call-diff-mode-button${diffMode() === "unified" ? " active" : ""}`}
aria-pressed={diffMode() === "unified"}
onClick={() => handleModeChange("unified")}
class="file-viewer-toolbar-icon-button"
onClick={() => handleModeChange(nextViewMode())}
aria-label={viewModeTitle()}
title={viewModeTitle()}
>
{params.t("toolCall.diff.viewMode.unified")}
{nextViewMode() === "split" ? <Split class="h-4 w-4" aria-hidden="true" /> : <AlignJustify class="h-4 w-4" aria-hidden="true" />}
</button>
<button
type="button"
class={`file-viewer-toolbar-icon-button${wordWrapEnabled() ? " active" : ""}`}
onClick={() => setWordWrapEnabled((enabled) => !enabled)}
aria-label={wordWrapTitle()}
title={wordWrapTitle()}
>
<WrapText class="h-4 w-4" aria-hidden="true" />
</button>
</div>
</div>
{cachedHtml ? (
<CachedDiffMarkup html={cachedHtml} onRendered={handleDiffRendered} />
{cachedHtml() ? (
<CachedDiffMarkup html={cachedHtml()!} onRendered={handleDiffRendered} />
) : (
<Suspense fallback={<pre class="tool-call-diff-fallback">{payload.diffText}</pre>}>
<LazyToolCallDiffViewer
diffText={payload.diffText}
filePath={payload.filePath}
theme={themeKey}
mode={diffMode()}
mode={currentMode()}
wrap={currentWrap()}
cacheEntryParams={cacheEntryParams as any}
onRendered={handleDiffRendered}
/>

View File

@@ -18,6 +18,11 @@ export const toolCallMessages = {
"toolCall.diff.viewMode.ariaLabel": "Diff view mode",
"toolCall.diff.viewMode.split": "Split",
"toolCall.diff.viewMode.unified": "Unified",
"toolCall.diff.switchToSplit": "Switch to split view",
"toolCall.diff.switchToUnified": "Switch to unified view",
"toolCall.diff.enableWordWrap": "Enable word wrap",
"toolCall.diff.disableWordWrap": "Disable word wrap",
"toolCall.diff.copyPatch": "Copy patch",
"toolCall.diagnostics.title": "Diagnostics",
"toolCall.diagnostics.ariaLabel": "Diagnostics",

View File

@@ -18,6 +18,11 @@ export const toolCallMessages = {
"toolCall.diff.viewMode.ariaLabel": "Modo de vista de diff",
"toolCall.diff.viewMode.split": "Dividida",
"toolCall.diff.viewMode.unified": "Unificada",
"toolCall.diff.switchToSplit": "Cambiar a vista dividida",
"toolCall.diff.switchToUnified": "Cambiar a vista unificada",
"toolCall.diff.enableWordWrap": "Activar ajuste de línea",
"toolCall.diff.disableWordWrap": "Desactivar ajuste de línea",
"toolCall.diff.copyPatch": "Copiar patch",
"toolCall.diagnostics.title": "Diagnósticos",
"toolCall.diagnostics.ariaLabel": "Diagnósticos",

View File

@@ -18,6 +18,11 @@ export const toolCallMessages = {
"toolCall.diff.viewMode.ariaLabel": "Mode d'affichage du diff",
"toolCall.diff.viewMode.split": "Côte à côte",
"toolCall.diff.viewMode.unified": "Unifié",
"toolCall.diff.switchToSplit": "Passer à la vue côte à côte",
"toolCall.diff.switchToUnified": "Passer à la vue unifiée",
"toolCall.diff.enableWordWrap": "Activer le retour à la ligne",
"toolCall.diff.disableWordWrap": "Désactiver le retour à la ligne",
"toolCall.diff.copyPatch": "Copier le patch",
"toolCall.diagnostics.title": "Diagnostics",
"toolCall.diagnostics.ariaLabel": "Diagnostics",

View File

@@ -18,6 +18,11 @@ export const toolCallMessages = {
"toolCall.diff.viewMode.ariaLabel": "מצב תצוגת diff",
"toolCall.diff.viewMode.split": "מפוצל",
"toolCall.diff.viewMode.unified": "מאוחד",
"toolCall.diff.switchToSplit": "עבור לתצוגה מפוצלת",
"toolCall.diff.switchToUnified": "עבור לתצוגה מאוחדת",
"toolCall.diff.enableWordWrap": "הפעל גלישת מילים",
"toolCall.diff.disableWordWrap": "כבה גלישת מילים",
"toolCall.diff.copyPatch": "העתק patch",
"toolCall.diagnostics.title": "אבחון",
"toolCall.diagnostics.ariaLabel": "אבחון",

View File

@@ -18,6 +18,11 @@ export const toolCallMessages = {
"toolCall.diff.viewMode.ariaLabel": "diff 表示モード",
"toolCall.diff.viewMode.split": "分割",
"toolCall.diff.viewMode.unified": "ユニファイド",
"toolCall.diff.switchToSplit": "分割表示に切り替え",
"toolCall.diff.switchToUnified": "ユニファイド表示に切り替え",
"toolCall.diff.enableWordWrap": "折り返しを有効化",
"toolCall.diff.disableWordWrap": "折り返しを無効化",
"toolCall.diff.copyPatch": "パッチをコピー",
"toolCall.diagnostics.title": "診断",
"toolCall.diagnostics.ariaLabel": "診断",

View File

@@ -18,6 +18,11 @@ export const toolCallMessages = {
"toolCall.diff.viewMode.ariaLabel": "Режим просмотра diff",
"toolCall.diff.viewMode.split": "Раздельный",
"toolCall.diff.viewMode.unified": "Единый",
"toolCall.diff.switchToSplit": "Переключить на раздельный вид",
"toolCall.diff.switchToUnified": "Переключить на единый вид",
"toolCall.diff.enableWordWrap": "Включить перенос слов",
"toolCall.diff.disableWordWrap": "Выключить перенос слов",
"toolCall.diff.copyPatch": "Скопировать patch",
"toolCall.diagnostics.title": "Диагностика",
"toolCall.diagnostics.ariaLabel": "Диагностика",

View File

@@ -18,6 +18,11 @@ export const toolCallMessages = {
"toolCall.diff.viewMode.ariaLabel": "Diff 视图模式",
"toolCall.diff.viewMode.split": "分栏",
"toolCall.diff.viewMode.unified": "统一",
"toolCall.diff.switchToSplit": "切换到分栏视图",
"toolCall.diff.switchToUnified": "切换到统一视图",
"toolCall.diff.enableWordWrap": "启用自动换行",
"toolCall.diff.disableWordWrap": "禁用自动换行",
"toolCall.diff.copyPatch": "复制补丁",
"toolCall.diagnostics.title": "诊断",
"toolCall.diagnostics.ariaLabel": "诊断",

View File

@@ -321,6 +321,7 @@
.tool-call-diff-shell {
padding: 0;
scrollbar-gutter: auto;
}
.tool-call-diff-viewer {
@@ -343,6 +344,8 @@
.tool-call-diff-shell .tool-call-diff-viewer {
max-height: none;
overflow: visible;
width: 100%;
min-width: 100%;
}
.tool-call-diff-toolbar-label {
@@ -513,6 +516,84 @@
font-size: var(--font-size-xs);
}
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-content,
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-hunk-content,
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-old-content,
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-new-content {
padding-right: 0 !important;
}
.tool-call-diff-shell[data-diff-mode="unified"] .tool-call-diff-viewer .diff-line-num {
padding-left: 1px !important;
padding-right: 1px !important;
text-align: left !important;
}
.tool-call-diff-shell[data-diff-mode="unified"] .tool-call-diff-viewer .unified-diff-table {
table-layout: fixed;
}
.tool-call-diff-shell[data-diff-mode="unified"] .tool-call-diff-viewer .unified-diff-table-num-col {
width: auto !important;
}
.tool-call-diff-shell[data-diff-mode="unified"] .tool-call-diff-viewer .tool-call-diff-compact-line-number {
display: block;
width: 100%;
overflow: hidden;
text-align: left;
white-space: nowrap;
}
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-old-num,
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-new-num,
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-hunk-action {
padding-left: 2px !important;
padding-right: 2px !important;
text-align: left !important;
}
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-old-num,
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-new-num,
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-old-num [data-line-num],
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-new-num [data-line-num] {
white-space: nowrap !important;
word-break: normal !important;
overflow-wrap: normal !important;
}
.tool-call-diff-shell[data-diff-mode="split"] .tool-call-diff-viewer .diff-line-hunk-action {
padding-top: 1px !important;
padding-bottom: 1px !important;
}
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-content-item {
padding-left: 1.1em !important;
}
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-content-operator {
margin-left: -1.1em !important;
width: 0.9em !important;
min-width: 0.9em !important;
text-indent: 0 !important;
}
@media (max-width: 640px) {
.tool-call-diff-shell[data-diff-mode="unified"] .tool-call-diff-viewer .unified-diff-table-wrapper {
--diff-aside-width: 18px;
}
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-content-item {
padding-left: 1.5em !important;
}
.tool-call-diff-shell .tool-call-diff-viewer .diff-line-content-operator {
margin-left: -1.5em !important;
width: 1.1em !important;
min-width: 1.1em !important;
}
}
.tool-call-markdown .markdown-code-block {
margin: 0;
border-radius: 0;

View File

@@ -40,6 +40,7 @@ export interface RenderCache {
html: string
theme?: string
mode?: string
wrap?: boolean
}
export interface PendingPermissionState {