diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0db139ff05a0..12fdfe601fc3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1020,6 +1020,9 @@ importers: debounce: specifier: ^2.1.1 version: 2.2.0 + diff: + specifier: ^5.2.0 + version: 5.2.0 fast-deep-equal: specifier: ^3.1.3 version: 3.1.3 @@ -1153,6 +1156,9 @@ importers: '@testing-library/user-event': specifier: ^14.6.1 version: 14.6.1(@testing-library/dom@10.4.0) + '@types/diff': + specifier: ^5.2.1 + version: 5.2.3 '@types/jest': specifier: ^29.0.0 version: 29.5.14 @@ -14020,7 +14026,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.57)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) '@vitest/utils@3.2.4': dependencies: diff --git a/src/core/diff/stats.ts b/src/core/diff/stats.ts new file mode 100644 index 000000000000..b842f5c04e88 --- /dev/null +++ b/src/core/diff/stats.ts @@ -0,0 +1,71 @@ +import { parsePatch, createTwoFilesPatch } from "diff" + +/** + * Diff utilities for backend (extension) use. + * Source of truth for diff normalization and stats. + */ + +export interface DiffStats { + added: number + removed: number +} + +/** + * Remove non-semantic diff noise like "No newline at end of file" + */ +export function sanitizeUnifiedDiff(diff: string): string { + if (!diff) return diff + return diff.replace(/\r\n/g, "\n").replace(/(^|\n)[ \t]*(?:\\ )?No newline at end of file[ \t]*(?=\n|$)/gi, "$1") +} + +/** + * Compute +/− counts from a unified diff (ignores headers/hunk lines) + */ +export function computeUnifiedDiffStats(diff?: string): DiffStats | null { + if (!diff) return null + + try { + const patches = parsePatch(diff) + if (!patches || patches.length === 0) return null + + let added = 0 + let removed = 0 + + for (const p of patches) { + for (const h of (p as any).hunks ?? []) { + for (const l of h.lines ?? []) { + const ch = (l as string)[0] + if (ch === "+") added++ + else if (ch === "-") removed++ + } + } + } + + if (added > 0 || removed > 0) return { added, removed } + return { added: 0, removed: 0 } + } catch { + // If parsing fails for any reason, signal no stats + return null + } +} + +/** + * Compute diff stats from any supported diff format (unified or search-replace) + * Tries unified diff format first, then falls back to search-replace format + */ +export function computeDiffStats(diff?: string): DiffStats | null { + if (!diff) return null + return computeUnifiedDiffStats(diff) +} + +/** + * Build a unified diff for a brand new file (all content lines are additions). + * Trailing newline is ignored for line counting and emission. + */ +export function convertNewFileToUnifiedDiff(content: string, filePath?: string): string { + const newFileName = filePath || "file" + // Normalize EOLs; rely on library for unified patch formatting + const normalized = (content || "").replace(/\r\n/g, "\n") + // Old file is empty (/dev/null), new file has content; zero context to show all lines as additions + return createTwoFilesPatch("/dev/null", newFileName, "", normalized, undefined, undefined, { context: 0 }) +} diff --git a/src/core/prompts/responses.ts b/src/core/prompts/responses.ts index 2f3ea87d4c6b..21703684b8bc 100644 --- a/src/core/prompts/responses.ts +++ b/src/core/prompts/responses.ts @@ -177,7 +177,9 @@ Otherwise, if you have not completed the task and do not need additional informa createPrettyPatch: (filename = "file", oldStr?: string, newStr?: string) => { // strings cannot be undefined or diff throws exception - const patch = diff.createPatch(filename.toPosix(), oldStr || "", newStr || "") + const patch = diff.createPatch(filename.toPosix(), oldStr || "", newStr || "", undefined, undefined, { + context: 3, + }) const lines = patch.split("\n") const prettyPatchLines = lines.slice(4) return prettyPatchLines.join("\n") diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts index dcdd13462401..1077b7bf3909 100644 --- a/src/core/tools/applyDiffTool.ts +++ b/src/core/tools/applyDiffTool.ts @@ -13,6 +13,7 @@ import { fileExistsAtPath } from "../../utils/fs" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { unescapeHtmlEntities } from "../../utils/text-normalization" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" +import { computeDiffStats, sanitizeUnifiedDiff } from "../diff/stats" export async function applyDiffToolLegacy( cline: Task, @@ -140,6 +141,11 @@ export async function applyDiffToolLegacy( cline.consecutiveMistakeCount = 0 cline.consecutiveMistakeCountForApplyDiff.delete(relPath) + // Generate backend-unified diff for display in chat/webview + const unifiedPatchRaw = formatResponse.createPrettyPatch(relPath, originalContent, diffResult.content) + const unifiedPatch = sanitizeUnifiedDiff(unifiedPatchRaw) + const diffStats = computeDiffStats(unifiedPatch) || undefined + // Check if preventFocusDisruption experiment is enabled const provider = cline.providerRef.deref() const state = await provider?.getState() @@ -158,6 +164,8 @@ export async function applyDiffToolLegacy( const completeMessage = JSON.stringify({ ...sharedMessageProps, diff: diffContent, + content: unifiedPatch, + diffStats, isProtected: isWriteProtected, } satisfies ClineSayTool) @@ -194,6 +202,8 @@ export async function applyDiffToolLegacy( const completeMessage = JSON.stringify({ ...sharedMessageProps, diff: diffContent, + content: unifiedPatch, + diffStats, isProtected: isWriteProtected, } satisfies ClineSayTool) diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts index e7d3a06ab92d..38ca309a3b33 100644 --- a/src/core/tools/insertContentTool.ts +++ b/src/core/tools/insertContentTool.ts @@ -12,6 +12,7 @@ import { fileExistsAtPath } from "../../utils/fs" import { insertGroups } from "../diff/insert-groups" import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" +import { convertNewFileToUnifiedDiff, computeDiffStats, sanitizeUnifiedDiff } from "../diff/stats" export async function insertContentTool( cline: Task, @@ -101,7 +102,7 @@ export async function insertContentTool( cline.diffViewProvider.originalContent = fileContent const lines = fileExists ? fileContent.split("\n") : [] - const updatedContent = insertGroups(lines, [ + let updatedContent = insertGroups(lines, [ { index: lineNumber - 1, elements: content.split("\n"), @@ -118,31 +119,31 @@ export async function insertContentTool( EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION, ) - // For consistency with writeToFileTool, handle new files differently - let diff: string | undefined - let approvalContent: string | undefined - + // Build unified diff for display (normalize EOLs only for diff generation) + let unified: string if (fileExists) { - // For existing files, generate diff and check for changes - diff = formatResponse.createPrettyPatch(relPath, fileContent, updatedContent) - if (!diff) { + const oldForDiff = fileContent.replace(/\r\n/g, "\n") + const newForDiff = updatedContent.replace(/\r\n/g, "\n") + unified = formatResponse.createPrettyPatch(relPath, oldForDiff, newForDiff) + if (!unified) { pushToolResult(`No changes needed for '${relPath}'`) return } - approvalContent = undefined } else { - // For new files, skip diff generation and provide full content - diff = undefined - approvalContent = updatedContent + const newForDiff = updatedContent.replace(/\r\n/g, "\n") + unified = convertNewFileToUnifiedDiff(newForDiff, relPath) } + unified = sanitizeUnifiedDiff(unified) + const diffStats = computeDiffStats(unified) || undefined // Prepare the approval message (same for both flows) const completeMessage = JSON.stringify({ ...sharedMessageProps, - diff, - content: approvalContent, + // Send unified diff as content for render-only webview + content: unified, lineNumber: lineNumber, isProtected: isWriteProtected, + diffStats, } satisfies ClineSayTool) // Show diff view if focus disruption prevention is disabled diff --git a/src/core/tools/multiApplyDiffTool.ts b/src/core/tools/multiApplyDiffTool.ts index a30778c5af0d..08bce08ede1d 100644 --- a/src/core/tools/multiApplyDiffTool.ts +++ b/src/core/tools/multiApplyDiffTool.ts @@ -15,6 +15,7 @@ import { unescapeHtmlEntities } from "../../utils/text-normalization" import { parseXmlForDiff } from "../../utils/xml" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" import { applyDiffToolLegacy } from "./applyDiffTool" +import { computeDiffStats, sanitizeUnifiedDiff } from "../diff/stats" interface DiffOperation { path: string @@ -282,31 +283,70 @@ Original error: ${errorMessage}` (opResult) => cline.rooProtectedController?.isWriteProtected(opResult.path) || false, ) - // Prepare batch diff data - const batchDiffs = operationsToApprove.map((opResult) => { + // Stream batch diffs progressively for better UX + const batchDiffs: Array<{ + path: string + changeCount: number + key: string + content: string + diffStats?: { added: number; removed: number } + diffs?: Array<{ content: string; startLine?: number }> + }> = [] + + for (const opResult of operationsToApprove) { const readablePath = getReadablePath(cline.cwd, opResult.path) const changeCount = opResult.diffItems?.length || 0 const changeText = changeCount === 1 ? "1 change" : `${changeCount} changes` - return { + let unified = "" + try { + const original = await fs.readFile(opResult.absolutePath!, "utf-8") + const processed = !cline.api.getModel().id.includes("claude") + ? (opResult.diffItems || []).map((item) => ({ + ...item, + content: item.content ? unescapeHtmlEntities(item.content) : item.content, + })) + : opResult.diffItems || [] + + const applyRes = + (await cline.diffStrategy?.applyDiff(original, processed)) ?? ({ success: false } as any) + const newContent = applyRes.success && applyRes.content ? applyRes.content : original + unified = formatResponse.createPrettyPatch(opResult.path, original, newContent) + } catch { + unified = "" + } + + const unifiedSanitized = sanitizeUnifiedDiff(unified) + const stats = computeDiffStats(unifiedSanitized) || undefined + batchDiffs.push({ path: readablePath, changeCount, key: `${readablePath} (${changeText})`, - content: opResult.path, // Full relative path + content: unifiedSanitized, + diffStats: stats, diffs: opResult.diffItems?.map((item) => ({ content: item.content, startLine: item.startLine, })), - } - }) + }) + + // Send a partial update after each file preview is ready + const partialMessage = JSON.stringify({ + tool: "appliedDiff", + batchDiffs, + isProtected: hasProtectedFiles, + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, true).catch(() => {}) + } + // Final approval message (non-partial) const completeMessage = JSON.stringify({ tool: "appliedDiff", batchDiffs, isProtected: hasProtectedFiles, } satisfies ClineSayTool) - const { response, text, images } = await cline.ask("tool", completeMessage, hasProtectedFiles) + const { response, text, images } = await cline.ask("tool", completeMessage, false) // Process batch response if (response === "yesButtonClicked") { @@ -418,6 +458,7 @@ Original error: ${errorMessage}` try { let originalContent: string | null = await fs.readFile(absolutePath, "utf-8") + let beforeContent: string | null = originalContent let successCount = 0 let formattedError = "" @@ -540,9 +581,13 @@ ${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""} if (operationsToApprove.length === 1) { // Prepare common data for single file operation const diffContents = diffItems.map((item) => item.content).join("\n\n") + const unifiedPatchRaw = formatResponse.createPrettyPatch(relPath, beforeContent!, originalContent!) + const unifiedPatch = sanitizeUnifiedDiff(unifiedPatchRaw) const operationMessage = JSON.stringify({ ...sharedMessageProps, diff: diffContents, + content: unifiedPatch, + diffStats: computeDiffStats(unifiedPatch) || undefined, } satisfies ClineSayTool) let toolProgressStatus diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts index 5abd96a20aff..b8e6da0caa29 100644 --- a/src/core/tools/writeToFileTool.ts +++ b/src/core/tools/writeToFileTool.ts @@ -16,6 +16,7 @@ import { detectCodeOmission } from "../../integrations/editor/detect-omission" import { unescapeHtmlEntities } from "../../utils/text-normalization" import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" +import { convertNewFileToUnifiedDiff, computeDiffStats, sanitizeUnifiedDiff } from "../diff/stats" export async function writeToFileTool( cline: Task, @@ -173,6 +174,15 @@ export async function writeToFileTool( if (isPreventFocusDisruptionEnabled) { // Direct file write without diff view + // Set up diffViewProvider properties needed for diff generation and saveDirectly + cline.diffViewProvider.editType = fileExists ? "modify" : "create" + if (fileExists) { + const absolutePath = path.resolve(cline.cwd, relPath) + cline.diffViewProvider.originalContent = await fs.readFile(absolutePath, "utf-8") + } else { + cline.diffViewProvider.originalContent = "" + } + // Check for code omissions before proceeding if (detectCodeOmission(cline.diffViewProvider.originalContent || "", newContent, predictedLineCount)) { if (cline.diffStrategy) { @@ -202,9 +212,15 @@ export async function writeToFileTool( } } + // Build unified diff for both existing and new files + let unified = fileExists + ? formatResponse.createPrettyPatch(relPath, cline.diffViewProvider.originalContent, newContent) + : convertNewFileToUnifiedDiff(newContent, relPath) + unified = sanitizeUnifiedDiff(unified) const completeMessage = JSON.stringify({ ...sharedMessageProps, - content: newContent, + content: unified, + diffStats: computeDiffStats(unified) || undefined, } satisfies ClineSayTool) const didApprove = await askApproval("tool", completeMessage, undefined, isWriteProtected) @@ -213,15 +229,6 @@ export async function writeToFileTool( return } - // Set up diffViewProvider properties needed for saveDirectly - cline.diffViewProvider.editType = fileExists ? "modify" : "create" - if (fileExists) { - const absolutePath = path.resolve(cline.cwd, relPath) - cline.diffViewProvider.originalContent = await fs.readFile(absolutePath, "utf-8") - } else { - cline.diffViewProvider.originalContent = "" - } - // Save directly without showing diff view or opening the file await cline.diffViewProvider.saveDirectly(relPath, newContent, false, diagnosticsEnabled, writeDelayMs) } else { @@ -275,12 +282,15 @@ export async function writeToFileTool( } } + // Build unified diff for both existing and new files + let unified = fileExists + ? formatResponse.createPrettyPatch(relPath, cline.diffViewProvider.originalContent, newContent) + : convertNewFileToUnifiedDiff(newContent, relPath) + unified = sanitizeUnifiedDiff(unified) const completeMessage = JSON.stringify({ ...sharedMessageProps, - content: fileExists ? undefined : newContent, - diff: fileExists - ? formatResponse.createPrettyPatch(relPath, cline.diffViewProvider.originalContent, newContent) - : undefined, + content: unified, + diffStats: computeDiffStats(unified) || undefined, } satisfies ClineSayTool) const didApprove = await askApproval("tool", completeMessage, undefined, isWriteProtected) diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 7d2759c91905..c3926d5073e6 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -386,6 +386,8 @@ export interface ClineSayTool { path?: string diff?: string content?: string + // Unified diff statistics computed by the extension + diffStats?: { added: number; removed: number } regex?: string filePattern?: string mode?: string @@ -407,6 +409,8 @@ export interface ClineSayTool { changeCount: number key: string content: string + // Per-file unified diff statistics computed by the extension + diffStats?: { added: number; removed: number } diffs?: Array<{ content: string startLine?: number diff --git a/webview-ui/package.json b/webview-ui/package.json index 9fda22097c94..a2d35432a451 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -41,6 +41,7 @@ "cmdk": "^1.0.0", "date-fns": "^4.1.0", "debounce": "^2.1.1", + "diff": "^5.2.0", "fast-deep-equal": "^3.1.3", "fzf": "^0.5.2", "hast-util-to-jsx-runtime": "^2.3.6", @@ -87,6 +88,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^14.6.1", + "@types/diff": "^5.2.1", "@types/jest": "^29.0.0", "@types/katex": "^0.16.7", "@types/node": "20.x", diff --git a/webview-ui/src/components/chat/BatchDiffApproval.tsx b/webview-ui/src/components/chat/BatchDiffApproval.tsx index 24ad8d489d9a..a88914cd88ad 100644 --- a/webview-ui/src/components/chat/BatchDiffApproval.tsx +++ b/webview-ui/src/components/chat/BatchDiffApproval.tsx @@ -6,6 +6,7 @@ interface FileDiff { changeCount: number key: string content: string + diffStats?: { added: number; removed: number } diffs?: Array<{ content: string startLine?: number @@ -35,17 +36,18 @@ export const BatchDiffApproval = memo(({ files = [], ts }: BatchDiffApprovalProp
children to render inline inside our table cell
+ const codeEl = hast?.children?.[0]?.children?.[0]
+ const inlineRoot =
+ codeEl && codeEl.children
+ ? { type: "element", tagName: "span", properties: {}, children: codeEl.children }
+ : { type: "element", tagName: "span", properties: {}, children: hast.children || [] }
+
+ return toJsxRuntime(inlineRoot as any, { Fragment, jsx, jsxs })
+ } catch {
+ return code
+ }
+ }
+
+ // Parse diff server-provided unified patch into renderable lines
+ const diffLines = useMemo(() => parseUnifiedDiff(source, filePath), [source, filePath])
+
+ return (
+
+
+
+
+ {diffLines.map((line, idx) => {
+ // Render compact separator between hunks
+ if (line.type === "gap") {
+ // Compact separator between hunks
+ return (
+
+
+
+
+ {/* +/- column (empty for gap) */}
+
+
+ {`${line.hiddenCount ?? 0} hidden lines`}
+
+
+ )
+ }
+
+ // Use VSCode's built-in diff editor color variables as classes for gutters
+ const gutterBgClass =
+ line.type === "addition"
+ ? "bg-[var(--vscode-diffEditor-insertedTextBackground)]"
+ : line.type === "deletion"
+ ? "bg-[var(--vscode-diffEditor-removedTextBackground)]"
+ : "bg-[var(--vscode-editorGroup-border)]"
+
+ const contentBgClass =
+ line.type === "addition"
+ ? "diff-content-inserted"
+ : line.type === "deletion"
+ ? "diff-content-removed"
+ : "diff-content-context"
+
+ const sign = line.type === "addition" ? "+" : line.type === "deletion" ? "-" : ""
+
+ return (
+
+ {/* Old line number */}
+
+ {line.oldLineNum || ""}
+
+ {/* New line number */}
+
+ {line.newLineNum || ""}
+
+ {/* Narrow colored gutter */}
+
+ {/* +/- fixed column to prevent wrapping into it */}
+
+ {sign}
+
+ {/* Code content (no +/- prefix here) */}
+
+ {renderHighlighted(line.content)}
+
+
+ )
+ })}
+
+
+
+
+ )
+})
+
+export default DiffView
diff --git a/webview-ui/src/index.css b/webview-ui/src/index.css
index 6f23892ced31..6355ded21be8 100644
--- a/webview-ui/src/index.css
+++ b/webview-ui/src/index.css
@@ -490,3 +490,28 @@ input[cmdk-input]:focus {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
+
+/* DiffView code font: use VS Code editor font and enable ligatures */
+.diff-view,
+.diff-view pre,
+.diff-view code,
+.diff-view .hljs {
+ font-family:
+ var(--vscode-editor-font-family), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
+ "Courier New", monospace;
+ font-variant-ligatures: contextual;
+ font-feature-settings:
+ "calt" 1,
+ "liga" 1;
+}
+
+/* DiffView background tints via CSS classes instead of inline styles */
+.diff-content-inserted {
+ background-color: color-mix(in srgb, var(--vscode-diffEditor-insertedTextBackground) 70%, transparent);
+}
+.diff-content-removed {
+ background-color: color-mix(in srgb, var(--vscode-diffEditor-removedTextBackground) 70%, transparent);
+}
+.diff-content-context {
+ background-color: color-mix(in srgb, var(--vscode-editorGroup-border) 100%, transparent);
+}
diff --git a/webview-ui/src/utils/parseUnifiedDiff.ts b/webview-ui/src/utils/parseUnifiedDiff.ts
new file mode 100644
index 000000000000..bed84c4ca9f5
--- /dev/null
+++ b/webview-ui/src/utils/parseUnifiedDiff.ts
@@ -0,0 +1,96 @@
+import { parsePatch } from "diff"
+
+export interface DiffLine {
+ oldLineNum: number | null
+ newLineNum: number | null
+ type: "context" | "addition" | "deletion" | "gap"
+ content: string
+ hiddenCount?: number
+}
+
+/**
+ * Parse a unified diff string into a flat list of renderable lines with
+ * line numbers, addition/deletion/context flags, and compact "gap" separators
+ * between hunks.
+ */
+export function parseUnifiedDiff(source: string, filePath?: string): DiffLine[] {
+ if (!source) return []
+
+ try {
+ const patches = parsePatch(source)
+ if (!patches || patches.length === 0) return []
+
+ const patch = filePath
+ ? (patches.find((p) =>
+ [p.newFileName, p.oldFileName].some(
+ (n) => typeof n === "string" && (n === filePath || (n as string).endsWith("/" + filePath)),
+ ),
+ ) ?? patches[0])
+ : patches[0]
+
+ if (!patch) return []
+
+ const lines: DiffLine[] = []
+ let prevHunk: any = null
+ for (const hunk of (patch as any).hunks || []) {
+ // Insert a compact "hidden lines" separator between hunks
+ if (prevHunk) {
+ const gapNew = hunk.newStart - (prevHunk.newStart + prevHunk.newLines)
+ const gapOld = hunk.oldStart - (prevHunk.oldStart + prevHunk.oldLines)
+ const hidden = Math.max(gapNew, gapOld)
+ if (hidden > 0) {
+ lines.push({
+ oldLineNum: null,
+ newLineNum: null,
+ type: "gap",
+ content: "",
+ hiddenCount: hidden,
+ })
+ }
+ }
+
+ let oldLine = hunk.oldStart
+ let newLine = hunk.newStart
+
+ for (const raw of hunk.lines || []) {
+ const firstChar = (raw as string)[0]
+ const content = (raw as string).slice(1)
+
+ if (firstChar === "-") {
+ lines.push({
+ oldLineNum: oldLine,
+ newLineNum: null,
+ type: "deletion",
+ content,
+ })
+ oldLine++
+ } else if (firstChar === "+") {
+ lines.push({
+ oldLineNum: null,
+ newLineNum: newLine,
+ type: "addition",
+ content,
+ })
+ newLine++
+ } else {
+ // Context line
+ lines.push({
+ oldLineNum: oldLine,
+ newLineNum: newLine,
+ type: "context",
+ content,
+ })
+ oldLine++
+ newLine++
+ }
+ }
+
+ prevHunk = hunk
+ }
+
+ return lines
+ } catch {
+ // swallow parse errors and render nothing rather than breaking the UI
+ return []
+ }
+}