diff --git a/src/cli/components/DiffRenderer.tsx b/src/cli/components/DiffRenderer.tsx
index 3cf7b12..a56b17c 100644
--- a/src/cli/components/DiffRenderer.tsx
+++ b/src/cli/components/DiffRenderer.tsx
@@ -1,6 +1,7 @@
import { relative } from "node:path";
import * as Diff from "diff";
import { Box, Text } from "ink";
+import { useTerminalWidth } from "../hooks/useTerminalWidth";
import { colors } from "./colors";
// Helper to format path as relative with ../
@@ -28,6 +29,7 @@ interface DiffLineProps {
type: "add" | "remove";
content: string;
compareContent?: string; // The other version to compare against for word diff
+ columns: number;
}
function DiffLine({
@@ -35,6 +37,7 @@ function DiffLine({
type,
content,
compareContent,
+ columns,
}: DiffLineProps) {
const prefix = type === "add" ? "+" : "-";
const lineBg =
@@ -42,6 +45,9 @@ function DiffLine({
const wordBg =
type === "add" ? colors.diff.addedWordBg : colors.diff.removedWordBg;
+ const prefixWidth = 1; // Single space prefix
+ const contentWidth = Math.max(0, columns - prefixWidth);
+
// If we have something to compare against, do word-level diff
if (compareContent !== undefined && content.trim() && compareContent.trim()) {
const wordDiffs =
@@ -50,60 +56,74 @@ function DiffLine({
: Diff.diffWords(content, compareContent);
return (
-
-
-
- {`${lineNumber} ${prefix} `}
-
- {wordDiffs.map((part, i) => {
- if (part.added && type === "add") {
- // This part was added (show with brighter background, black text)
- return (
-
- {part.value}
-
- );
- } else if (part.removed && type === "remove") {
- // This part was removed (show with brighter background, black text)
- return (
-
- {part.value}
-
- );
- } else if (!part.added && !part.removed) {
- // Unchanged part (show with line background, white text)
- return (
-
- {part.value}
-
- );
- }
- // Skip parts that don't belong in this line
- return null;
- })}
+
+
+
+
+
+
+
+ {`${lineNumber} ${prefix} `}
+
+ {wordDiffs.map((part, i) => {
+ if (part.added && type === "add") {
+ // This part was added (show with brighter background, black text)
+ return (
+
+ {part.value}
+
+ );
+ } else if (part.removed && type === "remove") {
+ // This part was removed (show with brighter background, black text)
+ return (
+
+ {part.value}
+
+ );
+ } else if (!part.added && !part.removed) {
+ // Unchanged part (show with line background, white text)
+ return (
+
+ {part.value}
+
+ );
+ }
+ // Skip parts that don't belong in this line
+ return null;
+ })}
+
+
);
}
// No comparison, just show the whole line with one background
return (
-
-
-
- {`${lineNumber} ${prefix} ${content}`}
-
+
+
+
+
+
+
+ {`${lineNumber} ${prefix} ${content}`}
+
+
);
}
@@ -114,10 +134,14 @@ interface WriteRendererProps {
}
export function WriteRenderer({ filePath, content }: WriteRendererProps) {
+ const columns = useTerminalWidth();
const relativePath = formatRelativePath(filePath);
const lines = content.split("\n");
const lineCount = lines.length;
+ const prefixWidth = 1; // Single space prefix
+ const contentWidth = Math.max(0, columns - prefixWidth);
+
return (
@@ -125,7 +149,14 @@ export function WriteRenderer({ filePath, content }: WriteRendererProps) {
⎿ Wrote {lineCount} line{lineCount !== 1 ? "s" : ""} to {relativePath}
{lines.map((line, i) => (
- {line}
+
+
+
+
+
+ {line}
+
+
))}
);
@@ -142,6 +173,7 @@ export function EditRenderer({
oldString,
newString,
}: EditRendererProps) {
+ const columns = useTerminalWidth();
const relativePath = formatRelativePath(filePath);
const oldLines = oldString.split("\n");
const newLines = newString.split("\n");
@@ -172,6 +204,7 @@ export function EditRenderer({
type="remove"
content={line}
compareContent={singleLineEdit ? newLines[0] : undefined}
+ columns={columns}
/>
))}
@@ -183,6 +216,7 @@ export function EditRenderer({
type="add"
content={line}
compareContent={singleLineEdit ? oldLines[0] : undefined}
+ columns={columns}
/>
))}
@@ -198,6 +232,7 @@ interface MultiEditRendererProps {
}
export function MultiEditRenderer({ filePath, edits }: MultiEditRendererProps) {
+ const columns = useTerminalWidth();
const relativePath = formatRelativePath(filePath);
// Count total additions and removals
@@ -238,6 +273,7 @@ export function MultiEditRenderer({ filePath, edits }: MultiEditRendererProps) {
compareContent={
singleLineEdit && i === 0 ? newLines[0] : undefined
}
+ columns={columns}
/>
))}
{newLines.map((line, i) => (
@@ -249,6 +285,7 @@ export function MultiEditRenderer({ filePath, edits }: MultiEditRendererProps) {
compareContent={
singleLineEdit && i === 0 ? oldLines[0] : undefined
}
+ columns={columns}
/>
))}
diff --git a/src/cli/components/MemoryDiffRenderer.tsx b/src/cli/components/MemoryDiffRenderer.tsx
index 8d7443f..125c77d 100644
--- a/src/cli/components/MemoryDiffRenderer.tsx
+++ b/src/cli/components/MemoryDiffRenderer.tsx
@@ -1,5 +1,6 @@
import * as Diff from "diff";
import { Box, Text } from "ink";
+import { useTerminalWidth } from "../hooks/useTerminalWidth";
import { colors } from "./colors";
interface MemoryDiffRendererProps {
@@ -15,6 +16,8 @@ export function MemoryDiffRenderer({
argsText,
toolName,
}: MemoryDiffRendererProps) {
+ const columns = useTerminalWidth();
+
try {
const args = JSON.parse(argsText);
@@ -22,7 +25,9 @@ export function MemoryDiffRenderer({
if (toolName === "memory_apply_patch") {
const label = args.label || "unknown";
const patch = args.patch || "";
- return ;
+ return (
+
+ );
}
// Handle memory tool (command-based)
@@ -41,6 +46,7 @@ export function MemoryDiffRenderer({
blockName={blockName}
oldStr={oldStr}
newStr={newStr}
+ columns={columns}
/>
);
}
@@ -48,6 +54,8 @@ export function MemoryDiffRenderer({
case "insert": {
const insertText = args.insert_text || "";
const insertLine = args.insert_line;
+ const prefixWidth = 4; // " " indent
+ const contentWidth = Math.max(0, columns - prefixWidth);
return (
@@ -57,14 +65,22 @@ export function MemoryDiffRenderer({
{insertLine !== undefined && ` at line ${insertLine}`}
{insertText.split("\n").map((line: string, i: number) => (
-
-
-
- {`+ ${line}`}
-
+
+
+ {" "}
+
+
+
+ {`+ ${line}`}
+
+
))}
@@ -74,6 +90,8 @@ export function MemoryDiffRenderer({
case "create": {
const description = args.description || "";
const fileText = args.file_text || "";
+ const prefixWidth = 4; // " " indent
+ const contentWidth = Math.max(0, columns - prefixWidth);
return (
@@ -88,14 +106,22 @@ export function MemoryDiffRenderer({
?.split("\n")
.slice(0, 3)
.map((line: string, i: number) => (
-
-
-
- {`+ ${truncate(line, 60)}`}
-
+
+
+ {" "}
+
+
+
+ {`+ ${truncate(line, 60)}`}
+
+
))}
{fileText && fileText.split("\n").length > 3 && (
@@ -162,10 +188,12 @@ function MemoryStrReplaceDiff({
blockName,
oldStr,
newStr,
+ columns,
}: {
blockName: string;
oldStr: string;
newStr: string;
+ columns: number;
}) {
const oldLines = oldStr.split("\n");
const newLines = newStr.split("\n");
@@ -192,6 +220,7 @@ function MemoryStrReplaceDiff({
type="remove"
content={line}
compareContent={singleLine ? newLines[0] : undefined}
+ columns={columns}
/>
))}
@@ -202,6 +231,7 @@ function MemoryStrReplaceDiff({
type="add"
content={line}
compareContent={singleLine ? oldLines[0] : undefined}
+ columns={columns}
/>
))}
@@ -217,10 +247,12 @@ function DiffLine({
type,
content,
compareContent,
+ columns,
}: {
type: "add" | "remove";
content: string;
compareContent?: string;
+ columns: number;
}) {
const prefix = type === "add" ? "+" : "-";
const lineBg =
@@ -228,6 +260,9 @@ function DiffLine({
const wordBg =
type === "add" ? colors.diff.addedWordBg : colors.diff.removedWordBg;
+ const prefixWidth = 4; // " " indent
+ const contentWidth = Math.max(0, columns - prefixWidth);
+
// Word-level diff if we have something to compare
if (compareContent !== undefined && content.trim() && compareContent.trim()) {
const wordDiffs =
@@ -236,56 +271,70 @@ function DiffLine({
: Diff.diffWords(content, compareContent);
return (
-
- {" "}
-
- {`${prefix} `}
-
- {wordDiffs.map((part, i) => {
- if (part.added && type === "add") {
- return (
-
- {part.value}
-
- );
- } else if (part.removed && type === "remove") {
- return (
-
- {part.value}
-
- );
- } else if (!part.added && !part.removed) {
- return (
-
- {part.value}
-
- );
- }
- return null;
- })}
+
+
+ {" "}
+
+
+
+
+ {`${prefix} `}
+
+ {wordDiffs.map((part, i) => {
+ if (part.added && type === "add") {
+ return (
+
+ {part.value}
+
+ );
+ } else if (part.removed && type === "remove") {
+ return (
+
+ {part.value}
+
+ );
+ } else if (!part.added && !part.removed) {
+ return (
+
+ {part.value}
+
+ );
+ }
+ return null;
+ })}
+
+
);
}
// Simple line without word diff
return (
-
- {" "}
-
- {`${prefix} ${content}`}
-
+
+
+ {" "}
+
+
+
+ {`${prefix} ${content}`}
+
+
);
}
@@ -298,12 +347,23 @@ function truncate(str: string, maxLen: number): string {
/**
* Renders a unified-diff patch from memory_apply_patch tool
*/
-function PatchDiffRenderer({ label, patch }: { label: string; patch: string }) {
+function PatchDiffRenderer({
+ label,
+ patch,
+ columns,
+}: {
+ label: string;
+ patch: string;
+ columns: number;
+}) {
const lines = patch.split("\n");
const maxLines = 8;
const displayLines = lines.slice(0, maxLines);
const hasMore = lines.length > maxLines;
+ const prefixWidth = 4; // " " indent
+ const contentWidth = Math.max(0, columns - prefixWidth);
+
return (
@@ -322,45 +382,74 @@ function PatchDiffRenderer({ label, patch }: { label: string; patch: string }) {
if (firstChar === "+") {
return (
-
- {" "}
-
- {`+ ${content}`}
-
+
+
+ {" "}
+
+
+
+ {`+ ${content}`}
+
+
);
} else if (firstChar === "-") {
return (
-
- {" "}
-
- {`- ${content}`}
-
+
+
+ {" "}
+
+
+
+ {`- ${content}`}
+
+
);
} else if (firstChar === " ") {
// Context line - show dimmed
return (
-
-
- {" "}
- {content}
-
+
+
+ {" "}
+
+
+
+ {content}
+
+
);
}
// Unknown format, show as-is
return (
-
- {" "}
- {line}
-
+
+
+ {" "}
+
+
+
+ {line}
+
+
+
);
})}
{hasMore && (
diff --git a/src/cli/components/PlanRenderer.tsx b/src/cli/components/PlanRenderer.tsx
index 5d0d0fe..17e5201 100644
--- a/src/cli/components/PlanRenderer.tsx
+++ b/src/cli/components/PlanRenderer.tsx
@@ -1,5 +1,6 @@
import { Box, Text } from "ink";
import type React from "react";
+import { useTerminalWidth } from "../hooks/useTerminalWidth.js";
import { colors } from "./colors.js";
interface PlanItem {
@@ -16,14 +17,22 @@ export const PlanRenderer: React.FC = ({
plan,
explanation,
}) => {
+ const columns = useTerminalWidth();
+ const prefixWidth = 5; // " ⎿ " or " "
+ const contentWidth = Math.max(0, columns - prefixWidth);
+
return (
{explanation && (
-
- {" ⎿ "}
-
- {explanation}
-
+
+
+ {" ⎿ "}
+
+
+
+ {explanation}
+
+
)}
{plan.map((item, index) => {
@@ -34,21 +43,21 @@ export const PlanRenderer: React.FC = ({
if (item.status === "completed") {
// Green with strikethrough
textElement = (
-
+
{checkbox} {item.step}
);
} else if (item.status === "in_progress") {
// Blue bold
textElement = (
-
+
{checkbox} {item.step}
);
} else {
// Plain text for pending
textElement = (
-
+
{checkbox} {item.step}
);
@@ -58,9 +67,13 @@ export const PlanRenderer: React.FC = ({
const prefix = index === 0 && !explanation ? " ⎿ " : " ";
return (
-
- {prefix}
- {textElement}
+
+
+ {prefix}
+
+
+ {textElement}
+
);
})}
diff --git a/src/cli/components/TodoRenderer.tsx b/src/cli/components/TodoRenderer.tsx
index b3a7fcf..a1fd0e0 100644
--- a/src/cli/components/TodoRenderer.tsx
+++ b/src/cli/components/TodoRenderer.tsx
@@ -1,5 +1,6 @@
import { Box, Text } from "ink";
import type React from "react";
+import { useTerminalWidth } from "../hooks/useTerminalWidth.js";
import { colors } from "./colors.js";
interface TodoItem {
@@ -14,6 +15,10 @@ interface TodoRendererProps {
}
export const TodoRenderer: React.FC = ({ todos }) => {
+ const columns = useTerminalWidth();
+ const prefixWidth = 5; // " ⎿ " or " "
+ const contentWidth = Math.max(0, columns - prefixWidth);
+
return (
{todos.map((todo, index) => {
@@ -24,21 +29,21 @@ export const TodoRenderer: React.FC = ({ todos }) => {
if (todo.status === "completed") {
// Green with strikethrough
textElement = (
-
+
{checkbox} {todo.content}
);
} else if (todo.status === "in_progress") {
// Blue bold (like code formatting)
textElement = (
-
+
{checkbox} {todo.content}
);
} else {
// Plain text for pending
textElement = (
-
+
{checkbox} {todo.content}
);
@@ -48,9 +53,13 @@ export const TodoRenderer: React.FC = ({ todos }) => {
const prefix = index === 0 ? " ⎿ " : " ";
return (
-
- {prefix}
- {textElement}
+
+
+ {prefix}
+
+
+ {textElement}
+
);
})}