replicas-cli 0.2.52 → 0.2.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,15 +11,15 @@ import {
11
11
  isAgentBackendEvent,
12
12
  parseAgentEvents,
13
13
  parseUserMessage
14
- } from "./chunk-QIZFFIVG.mjs";
14
+ } from "./chunk-SDULF4LX.mjs";
15
15
 
16
16
  // src/interactive/index.tsx
17
17
  import { createCliRenderer } from "@opentui/core";
18
18
  import { createRoot } from "@opentui/react";
19
19
 
20
20
  // src/interactive/App.tsx
21
- import { useState as useState7, useEffect as useEffect3, useMemo as useMemo5, useCallback as useCallback6, useRef as useRef3 } from "react";
22
- import { useKeyboard as useKeyboard4, useRenderer, useTerminalDimensions as useTerminalDimensions3 } from "@opentui/react";
21
+ import { useState as useState8, useEffect as useEffect3, useMemo as useMemo6, useCallback as useCallback7, useRef as useRef5 } from "react";
22
+ import { useKeyboard as useKeyboard5, useRenderer, useTerminalDimensions as useTerminalDimensions3 } from "@opentui/react";
23
23
  import { QueryClient } from "@tanstack/react-query";
24
24
 
25
25
  // ../shared/src/hooks/auth-context.ts
@@ -114,7 +114,8 @@ function useWorkspaces(page = 1, limit = 100, scope = "organization") {
114
114
  return useQuery({
115
115
  queryKey: ["workspaces", orgId, scope, page, limit],
116
116
  queryFn: () => orgFetch(`/v1/workspaces?scope=${scope}&page=${page}&limit=${limit}`),
117
- enabled: !!orgId
117
+ enabled: !!orgId,
118
+ staleTime: 1e4
118
119
  }, auth.queryClient);
119
120
  }
120
121
  function useCreateWorkspace() {
@@ -493,7 +494,18 @@ var KEYBINDS = {
493
494
  "chat-input": "\u21B5 send \u21E5 plan/build",
494
495
  info: "j/k nav \u21B5 open o dashboard w wake"
495
496
  };
496
- function StatusBar({ focusPanel }) {
497
+ var DIFF_KEYBINDS = "j/k nav \u21E5 switch pane";
498
+ var MODE_HINT = "1 chat 2 diff";
499
+ var MODE_HINT_PANELS = /* @__PURE__ */ new Set([
500
+ "chat-tabs",
501
+ "chat-history",
502
+ "info"
503
+ ]);
504
+ function StatusBar({ focusPanel, viewingDiff, hasDiffAvailable }) {
505
+ const showingDiffHints = viewingDiff && focusPanel !== "sidebar" && focusPanel !== "info";
506
+ const baseHints = showingDiffHints ? DIFF_KEYBINDS : KEYBINDS[focusPanel];
507
+ const showModeHint = hasDiffAvailable && (showingDiffHints || MODE_HINT_PANELS.has(focusPanel));
508
+ const hints = showModeHint ? `${baseHints} ${MODE_HINT}` : baseHints;
497
509
  return /* @__PURE__ */ jsxs(
498
510
  "box",
499
511
  {
@@ -507,7 +519,7 @@ function StatusBar({ focusPanel }) {
507
519
  /* @__PURE__ */ jsx("text", { children: /* @__PURE__ */ jsx("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx("strong", { children: "Replicas" }) }) }),
508
520
  /* @__PURE__ */ jsxs("text", { children: [
509
521
  /* @__PURE__ */ jsxs("span", { fg: "#555555", children: [
510
- KEYBINDS[focusPanel],
522
+ hints,
511
523
  " "
512
524
  ] }),
513
525
  /* @__PURE__ */ jsxs("span", { fg: "#888888", children: [
@@ -527,11 +539,11 @@ function StatusBar({ focusPanel }) {
527
539
  import { useState as useState2, useMemo as useMemo2, useCallback as useCallback3 } from "react";
528
540
  import { useKeyboard, useTerminalDimensions } from "@opentui/react";
529
541
  import { jsx as jsx2, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
530
- function flattenGroups(groups, expandedGroups) {
542
+ function flattenGroups(groups, collapsedGroups) {
531
543
  const items = [];
532
544
  for (const group of groups) {
533
545
  if (group.id === "__ungrouped__" && group.workspaces.length === 0) continue;
534
- const expanded = expandedGroups.has(group.id);
546
+ const expanded = !collapsedGroups.has(group.id);
535
547
  items.push({ type: "group", id: group.id, name: group.name, groupType: group.type, expanded });
536
548
  if (expanded) {
537
549
  for (const ws of group.workspaces) {
@@ -566,20 +578,11 @@ function WorkspaceSidebar({
566
578
  loading
567
579
  }) {
568
580
  const { height: termHeight } = useTerminalDimensions();
569
- const [expandedGroups, setExpandedGroups] = useState2(
570
- () => new Set(groups.map((g) => g.id))
571
- );
581
+ const [collapsedGroups, setCollapsedGroups] = useState2(/* @__PURE__ */ new Set());
572
582
  const [cursorIndex, setCursorIndex] = useState2(0);
573
583
  const [scrollOffset, setScrollOffset] = useState2(0);
574
584
  const [confirmDelete, setConfirmDelete] = useState2(null);
575
- const expandedWithNew = useMemo2(() => {
576
- const next = new Set(expandedGroups);
577
- for (const g of groups) {
578
- if (!next.has(g.id)) next.add(g.id);
579
- }
580
- return next;
581
- }, [expandedGroups, groups]);
582
- const items = useMemo2(() => flattenGroups(groups, expandedWithNew), [groups, expandedWithNew]);
585
+ const items = useMemo2(() => flattenGroups(groups, collapsedGroups), [groups, collapsedGroups]);
583
586
  const clampedCursor = Math.min(cursorIndex, Math.max(0, items.length - 1));
584
587
  if (clampedCursor !== cursorIndex) {
585
588
  setCursorIndex(clampedCursor);
@@ -610,7 +613,7 @@ function WorkspaceSidebar({
610
613
  const handleAction = useCallback3(
611
614
  (item) => {
612
615
  if (item.type === "group") {
613
- setExpandedGroups((prev) => {
616
+ setCollapsedGroups((prev) => {
614
617
  const next = new Set(prev);
615
618
  if (next.has(item.id)) {
616
619
  next.delete(item.id);
@@ -853,12 +856,11 @@ function WorkspaceSidebar({
853
856
  }
854
857
 
855
858
  // src/interactive/components/ChatArea.tsx
856
- import { useRef, useEffect as useEffect2 } from "react";
857
- import { useKeyboard as useKeyboard2 } from "@opentui/react";
859
+ import { useRef as useRef2, useEffect as useEffect2 } from "react";
860
+ import { useKeyboard as useKeyboard3 } from "@opentui/react";
858
861
 
859
862
  // src/interactive/components/ChatMessage.tsx
860
- import { useState as useState4, useMemo as useMemo3 } from "react";
861
- import { SyntaxStyle } from "@opentui/core";
863
+ import { useState as useState5, useMemo as useMemo4 } from "react";
862
864
 
863
865
  // src/interactive/components/Spinner.tsx
864
866
  import "opentui-spinner/react";
@@ -873,168 +875,678 @@ function SpinnerLabel({ color, label }) {
873
875
  }
874
876
 
875
877
  // src/interactive/components/StructuredUserMessage.tsx
876
- import { useState as useState3 } from "react";
878
+ import { useState as useState4 } from "react";
879
+
880
+ // src/interactive/components/diff-utils.tsx
881
+ import React2, { useState as useState3, useMemo as useMemo3, useCallback as useCallback4, useRef } from "react";
882
+ import { SyntaxStyle } from "@opentui/core";
883
+ import { useKeyboard as useKeyboard2 } from "@opentui/react";
877
884
  import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
885
+ var sharedSyntaxStyle = null;
886
+ function getSyntaxStyle() {
887
+ if (!sharedSyntaxStyle) {
888
+ sharedSyntaxStyle = SyntaxStyle.fromTheme([
889
+ { scope: ["markup.heading", "markup.heading.1", "markup.heading.2", "markup.heading.3"], style: { foreground: "#66bb6a", bold: true } },
890
+ { scope: ["markup.bold", "markup.strong"], style: { foreground: "#ffffff", bold: true } },
891
+ { scope: ["markup.italic", "markup.emphasis"], style: { foreground: "#e0e0e0", italic: true } },
892
+ { scope: ["markup.link"], style: { foreground: "#7dcfff", underline: true } },
893
+ { scope: ["markup.link.url"], style: { foreground: "#7dcfff", underline: true } },
894
+ { scope: ["markup.link.text"], style: { foreground: "#66bb6a", underline: true } },
895
+ { scope: ["markup.link.label"], style: { foreground: "#66bb6a", underline: true } },
896
+ { scope: ["markup.list"], style: { foreground: "#66bb6a" } },
897
+ { scope: ["markup.quote"], style: { foreground: "#888888", italic: true } },
898
+ { scope: ["markup.raw", "markup.raw.block"], style: { foreground: "#bb9af7" } },
899
+ { scope: ["markup.raw.inline"], style: { foreground: "#bb9af7", background: "#1a1a2e" } },
900
+ { scope: ["keyword", "keyword.control", "keyword.operator"], style: { foreground: "#bb9af7" } },
901
+ { scope: ["string", "string.quoted"], style: { foreground: "#9ece6a" } },
902
+ { scope: ["comment", "comment.line", "comment.block"], style: { foreground: "#555555", italic: true } },
903
+ { scope: ["constant", "constant.numeric", "constant.language"], style: { foreground: "#ff9e64" } },
904
+ { scope: ["entity.name.function", "support.function"], style: { foreground: "#7aa2f7" } },
905
+ { scope: ["entity.name.type", "support.type"], style: { foreground: "#2ac3de" } },
906
+ { scope: ["variable", "variable.other"], style: { foreground: "#c0caf5" } },
907
+ { scope: ["punctuation"], style: { foreground: "#888888" } }
908
+ ]);
909
+ }
910
+ return sharedSyntaxStyle;
911
+ }
912
+ function truncateName(text, maxLen) {
913
+ if (maxLen <= 0) return "";
914
+ if (text.length <= maxLen) return text;
915
+ if (maxLen === 1) return "\u2026";
916
+ return text.slice(0, maxLen - 1) + "\u2026";
917
+ }
918
+ function countDiffStats(diff) {
919
+ let added = 0;
920
+ let removed = 0;
921
+ for (const line of diff.split("\n")) {
922
+ if (line.startsWith("+") && !line.startsWith("+++")) added++;
923
+ else if (line.startsWith("-") && !line.startsWith("---")) removed++;
924
+ }
925
+ return { added, removed };
926
+ }
927
+ var EXT_TO_FILETYPE = {
928
+ ts: "typescript",
929
+ tsx: "typescriptreact",
930
+ js: "javascript",
931
+ jsx: "javascriptreact",
932
+ cts: "typescript",
933
+ mts: "typescript",
934
+ cjs: "javascript",
935
+ mjs: "javascript",
936
+ py: "python",
937
+ rs: "rust",
938
+ go: "go",
939
+ rb: "ruby",
940
+ java: "java",
941
+ kt: "kotlin",
942
+ swift: "swift",
943
+ c: "c",
944
+ cpp: "cpp",
945
+ h: "c",
946
+ hpp: "cpp",
947
+ cs: "csharp",
948
+ css: "css",
949
+ scss: "scss",
950
+ html: "html",
951
+ json: "json",
952
+ yaml: "yaml",
953
+ yml: "yaml",
954
+ toml: "toml",
955
+ md: "markdown",
956
+ sh: "bash",
957
+ bash: "bash",
958
+ zsh: "bash",
959
+ sql: "sql"
960
+ };
961
+ function extFromPath(path) {
962
+ const ext = path.split(".").pop()?.toLowerCase();
963
+ return ext ? EXT_TO_FILETYPE[ext] : void 0;
964
+ }
965
+ function ensureUnifiedHeaders(diff, filePath) {
966
+ if (/^--- /m.test(diff) && /^@@ /m.test(diff)) return diff;
967
+ const file = filePath ?? "a";
968
+ const fileHeaders = `--- a/${file}
969
+ +++ b/${file}
970
+ `;
971
+ if (/^@@ -\d+/m.test(diff)) {
972
+ return fileHeaders + diff;
973
+ }
974
+ const lines = diff.split("\n");
975
+ const diffLines = lines.filter((l) => !l.startsWith("@@"));
976
+ let oldCount = 0;
977
+ let newCount = 0;
978
+ for (const line of diffLines) {
979
+ if (line.startsWith("+") && !line.startsWith("+++")) newCount++;
980
+ else if (line.startsWith("-") && !line.startsWith("---")) oldCount++;
981
+ else if (line.startsWith(" ") || !line.startsWith("+") && !line.startsWith("-") && line.length > 0) {
982
+ oldCount++;
983
+ newCount++;
984
+ }
985
+ }
986
+ const hunk = `@@ -1,${oldCount} +1,${newCount} @@`;
987
+ return `${fileHeaders}${hunk}
988
+ ${diffLines.join("\n")}`;
989
+ }
990
+ function diffProps(filePath) {
991
+ return {
992
+ view: "split",
993
+ filetype: filePath ? extFromPath(filePath) : void 0,
994
+ syntaxStyle: getSyntaxStyle(),
995
+ showLineNumbers: true,
996
+ width: "100%",
997
+ wrapMode: "word",
998
+ fg: "#cccccc",
999
+ addedBg: "#0d2b0d",
1000
+ removedBg: "#2b0d0d",
1001
+ contextBg: "#0a0a0a",
1002
+ addedSignColor: "#66bb6a",
1003
+ removedSignColor: "#ff4444",
1004
+ lineNumberFg: "#555555",
1005
+ lineNumberBg: "#0a0a0a",
1006
+ addedLineNumberBg: "#0d2b0d",
1007
+ removedLineNumberBg: "#2b0d0d"
1008
+ };
1009
+ }
1010
+ function DiffView({ diff, filePath }) {
1011
+ const hunks = useMemo3(() => splitDiffByHunks(diff), [diff]);
1012
+ const props = diffProps(filePath);
1013
+ if (hunks.length <= 1) {
1014
+ return /* @__PURE__ */ jsx4("diff", { diff: ensureUnifiedHeaders(diff, filePath), ...props });
1015
+ }
1016
+ return /* @__PURE__ */ jsx4("box", { flexDirection: "column", children: hunks.map((hunk, i) => {
1017
+ const prev = i > 0 ? hunks[i - 1] : null;
1018
+ const gap = prev ? hunk.newStart - (prev.newStart + prev.newCount) : 0;
1019
+ return /* @__PURE__ */ jsxs4(React2.Fragment, { children: [
1020
+ prev && gap > 0 && /* @__PURE__ */ jsx4("box", { backgroundColor: "#151515", paddingX: 1, height: 1, children: /* @__PURE__ */ jsx4("text", { children: /* @__PURE__ */ jsxs4("span", { fg: "#666666", children: [
1021
+ gap,
1022
+ " unmodified ",
1023
+ gap === 1 ? "line" : "lines"
1024
+ ] }) }) }),
1025
+ /* @__PURE__ */ jsx4("diff", { diff: ensureUnifiedHeaders(hunk.body, filePath), ...props })
1026
+ ] }, i);
1027
+ }) });
1028
+ }
1029
+ function splitDiffByFile(fullDiff) {
1030
+ const chunks = [];
1031
+ const lines = fullDiff.split("\n");
1032
+ let currentLines = [];
1033
+ let currentPath = "";
1034
+ for (const line of lines) {
1035
+ if (line.startsWith("diff --git ")) {
1036
+ if (currentPath && currentLines.length > 0) {
1037
+ const diff = currentLines.join("\n");
1038
+ chunks.push({ path: currentPath, diff, ...countDiffStats(diff) });
1039
+ }
1040
+ const match = line.match(/^diff --git a\/(.+) b\/(.+)$/);
1041
+ currentPath = match ? match[2] : "";
1042
+ currentLines = [line];
1043
+ } else {
1044
+ currentLines.push(line);
1045
+ }
1046
+ }
1047
+ if (currentPath && currentLines.length > 0) {
1048
+ const diff = currentLines.join("\n");
1049
+ chunks.push({ path: currentPath, diff, ...countDiffStats(diff) });
1050
+ }
1051
+ return chunks;
1052
+ }
1053
+ function splitDiffByHunks(fileDiff) {
1054
+ const hunks = [];
1055
+ const lines = fileDiff.split("\n");
1056
+ let current = null;
1057
+ for (const line of lines) {
1058
+ const match = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
1059
+ if (match) {
1060
+ if (current) hunks.push({ body: current.lines.join("\n"), newStart: current.newStart, newCount: current.newCount });
1061
+ current = {
1062
+ lines: [line],
1063
+ newStart: parseInt(match[1], 10),
1064
+ newCount: match[2] ? parseInt(match[2], 10) : 1
1065
+ };
1066
+ } else if (current) {
1067
+ current.lines.push(line);
1068
+ }
1069
+ }
1070
+ if (current) hunks.push({ body: current.lines.join("\n"), newStart: current.newStart, newCount: current.newCount });
1071
+ return hunks;
1072
+ }
1073
+ function buildFileTree(files) {
1074
+ const root = { name: "", children: /* @__PURE__ */ new Map() };
1075
+ files.forEach((file, idx) => {
1076
+ const segments = file.path.split("/");
1077
+ let node = root;
1078
+ for (const seg of segments) {
1079
+ if (!node.children.has(seg)) {
1080
+ node.children.set(seg, { name: seg, children: /* @__PURE__ */ new Map() });
1081
+ }
1082
+ node = node.children.get(seg);
1083
+ }
1084
+ node.fileIndex = idx;
1085
+ node.added = file.added;
1086
+ node.removed = file.removed;
1087
+ });
1088
+ const result = [];
1089
+ function walk(node, depth, accumulatedPath, parentPath) {
1090
+ if (node.fileIndex !== void 0) {
1091
+ const fullPath = parentPath ? `${parentPath}/${node.name}` : node.name;
1092
+ result.push({
1093
+ type: "file",
1094
+ name: node.name,
1095
+ path: fullPath,
1096
+ depth,
1097
+ fileIndex: node.fileIndex,
1098
+ added: node.added,
1099
+ removed: node.removed
1100
+ });
1101
+ return;
1102
+ }
1103
+ const children = Array.from(node.children.values());
1104
+ if (children.length === 1 && children[0].fileIndex === void 0) {
1105
+ walk(children[0], depth, [...accumulatedPath, node.name], parentPath);
1106
+ return;
1107
+ }
1108
+ let folderFullPath = parentPath;
1109
+ if (node.name !== "") {
1110
+ const displayName = [...accumulatedPath, node.name].filter(Boolean).join("/");
1111
+ folderFullPath = parentPath ? `${parentPath}/${displayName}` : displayName;
1112
+ result.push({ type: "folder", name: displayName, path: folderFullPath, depth });
1113
+ }
1114
+ const childDepth = node.name === "" ? depth : depth + 1;
1115
+ for (const child of children) {
1116
+ walk(child, childDepth, [], folderFullPath);
1117
+ }
1118
+ }
1119
+ walk(root, 0, [], "");
1120
+ return result;
1121
+ }
1122
+ var FILE_PANEL_WIDTH = 32;
1123
+ function DiffViewer({ diff, repoName, focused }) {
1124
+ const files = useMemo3(() => splitDiffByFile(diff), [diff]);
1125
+ const tree = useMemo3(() => buildFileTree(files), [files]);
1126
+ const [selectedFileIndex, setSelectedFileIndex] = useState3(0);
1127
+ const [collapsedFolders, setCollapsedFolders] = useState3(/* @__PURE__ */ new Set());
1128
+ const [cursorIndex, setCursorIndex] = useState3(0);
1129
+ const [innerFocus, setInnerFocus] = useState3("files");
1130
+ const safeFileIndex = Math.min(selectedFileIndex, Math.max(0, files.length - 1));
1131
+ const diffScrollRef = useRef(null);
1132
+ const fileTreeScrollRef = useRef(null);
1133
+ const visibleTree = useMemo3(() => {
1134
+ const result = [];
1135
+ let hideUntilDepth = null;
1136
+ for (const node of tree) {
1137
+ if (hideUntilDepth !== null && node.depth <= hideUntilDepth) {
1138
+ hideUntilDepth = null;
1139
+ }
1140
+ if (hideUntilDepth !== null) continue;
1141
+ result.push(node);
1142
+ if (node.type === "folder" && collapsedFolders.has(node.path)) {
1143
+ hideUntilDepth = node.depth;
1144
+ }
1145
+ }
1146
+ return result;
1147
+ }, [tree, collapsedFolders]);
1148
+ const safeCursorIndex = Math.min(cursorIndex, Math.max(0, visibleTree.length - 1));
1149
+ const scrollCursorIntoView = useCallback4((treeIdx) => {
1150
+ const node = visibleTree[treeIdx];
1151
+ if (!node) return;
1152
+ fileTreeScrollRef.current?.scrollChildIntoView(`tree-${node.path}`);
1153
+ }, [visibleTree]);
1154
+ const moveCursor = useCallback4((next) => {
1155
+ const len = visibleTree.length;
1156
+ if (len === 0) return;
1157
+ const wrapped = (next % len + len) % len;
1158
+ setCursorIndex(wrapped);
1159
+ scrollCursorIntoView(wrapped);
1160
+ const node = visibleTree[wrapped];
1161
+ if (node?.type === "file" && node.fileIndex !== void 0) {
1162
+ setSelectedFileIndex(node.fileIndex);
1163
+ }
1164
+ }, [visibleTree, scrollCursorIntoView]);
1165
+ const toggleFolder = useCallback4((folderPath) => {
1166
+ setCollapsedFolders((prev) => {
1167
+ const next = new Set(prev);
1168
+ if (next.has(folderPath)) next.delete(folderPath);
1169
+ else next.add(folderPath);
1170
+ return next;
1171
+ });
1172
+ }, []);
1173
+ const selectFile = useCallback4((fileIndex, treeIndex) => {
1174
+ setSelectedFileIndex(fileIndex);
1175
+ setCursorIndex(treeIndex);
1176
+ scrollCursorIntoView(treeIndex);
1177
+ }, [scrollCursorIntoView]);
1178
+ useKeyboard2((key) => {
1179
+ if (!focused) return;
1180
+ if (key.name === "tab" && !key.shift) {
1181
+ setInnerFocus((f) => f === "files" ? "diff" : "files");
1182
+ return;
1183
+ }
1184
+ if (innerFocus === "files") {
1185
+ if (visibleTree.length > 1 && (key.name === "up" || key.name === "k" && !key.ctrl)) {
1186
+ moveCursor(safeCursorIndex - 1);
1187
+ return;
1188
+ }
1189
+ if (visibleTree.length > 1 && (key.name === "down" || key.name === "j" && !key.ctrl)) {
1190
+ moveCursor(safeCursorIndex + 1);
1191
+ return;
1192
+ }
1193
+ if (key.name === "return" || key.name === "enter") {
1194
+ const node = visibleTree[safeCursorIndex];
1195
+ if (node?.type === "folder") toggleFolder(node.path);
1196
+ return;
1197
+ }
1198
+ return;
1199
+ }
1200
+ if (innerFocus === "diff") {
1201
+ const sb = diffScrollRef.current;
1202
+ if (!sb) return;
1203
+ if (key.ctrl && key.name === "d") {
1204
+ sb.scrollBy((sb.viewport?.height ?? 20) / 2);
1205
+ } else if (key.ctrl && key.name === "u") {
1206
+ sb.scrollBy(-((sb.viewport?.height ?? 20) / 2));
1207
+ }
1208
+ }
1209
+ });
1210
+ const statsColumnWidth = useMemo3(() => {
1211
+ let max = 0;
1212
+ for (const f of files) {
1213
+ const a = f.added > 0 ? `+${f.added}`.length : 0;
1214
+ const r = f.removed > 0 ? `-${f.removed}`.length : 0;
1215
+ const len = a + r + (a > 0 && r > 0 ? 1 : 0);
1216
+ if (len > max) max = len;
1217
+ }
1218
+ return max;
1219
+ }, [files]);
1220
+ if (files.length === 0) {
1221
+ return /* @__PURE__ */ jsxs4("box", { flexGrow: 1, flexDirection: "column", children: [
1222
+ /* @__PURE__ */ jsxs4("box", { paddingX: 1, marginBottom: 1, flexDirection: "row", justifyContent: "space-between", flexShrink: 0, height: 1, children: [
1223
+ /* @__PURE__ */ jsxs4("text", { children: [
1224
+ /* @__PURE__ */ jsx4("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx4("strong", { children: "Diff" }) }),
1225
+ /* @__PURE__ */ jsxs4("span", { fg: "#555555", children: [
1226
+ " ",
1227
+ "\u2502",
1228
+ " "
1229
+ ] }),
1230
+ /* @__PURE__ */ jsx4("span", { fg: "#cccccc", children: repoName })
1231
+ ] }),
1232
+ /* @__PURE__ */ jsx4("text", { fg: "#555555", children: "1 file" })
1233
+ ] }),
1234
+ /* @__PURE__ */ jsx4("scrollbox", { flexGrow: 1, focused, children: /* @__PURE__ */ jsx4("box", { paddingX: 1, children: /* @__PURE__ */ jsx4(DiffView, { diff }) }) })
1235
+ ] });
1236
+ }
1237
+ const selected = files[safeFileIndex];
1238
+ const totalAdded = files.reduce((s, f) => s + f.added, 0);
1239
+ const totalRemoved = files.reduce((s, f) => s + f.removed, 0);
1240
+ const filesPaneActive = focused === true && innerFocus === "files";
1241
+ const diffPaneActive = focused === true && innerFocus === "diff";
1242
+ return /* @__PURE__ */ jsxs4("box", { flexGrow: 1, flexDirection: "row", children: [
1243
+ /* @__PURE__ */ jsxs4(
1244
+ "box",
1245
+ {
1246
+ width: FILE_PANEL_WIDTH,
1247
+ flexDirection: "column",
1248
+ backgroundColor: "#0a0a0a",
1249
+ flexShrink: 0,
1250
+ children: [
1251
+ /* @__PURE__ */ jsx4("box", { paddingX: 1, flexShrink: 0, height: 1, children: /* @__PURE__ */ jsxs4("text", { children: [
1252
+ /* @__PURE__ */ jsx4("span", { fg: filesPaneActive ? "#66bb6a" : "#333333", children: filesPaneActive ? "\u25B8 " : " " }),
1253
+ /* @__PURE__ */ jsx4("span", { fg: filesPaneActive ? "#66bb6a" : "#888888", children: filesPaneActive ? /* @__PURE__ */ jsxs4("strong", { children: [
1254
+ files.length,
1255
+ " ",
1256
+ files.length === 1 ? "file" : "files",
1257
+ " changed"
1258
+ ] }) : `${files.length} ${files.length === 1 ? "file" : "files"} changed` })
1259
+ ] }) }),
1260
+ /* @__PURE__ */ jsx4("scrollbox", { ref: fileTreeScrollRef, flexGrow: 1, focused: false, children: visibleTree.map((node, i) => {
1261
+ const indent = " ".repeat(node.depth);
1262
+ const usable = FILE_PANEL_WIDTH - 2;
1263
+ const isCursor = filesPaneActive && i === safeCursorIndex;
1264
+ if (node.type === "folder") {
1265
+ const isCollapsed = collapsedFolders.has(node.path);
1266
+ const chevron = isCollapsed ? "\u25B8" : "\u25BE";
1267
+ const overhead2 = indent.length + 2;
1268
+ const nameMax2 = Math.max(1, usable - overhead2);
1269
+ const displayName2 = truncateName(node.name, nameMax2);
1270
+ return /* @__PURE__ */ jsx4(
1271
+ "box",
1272
+ {
1273
+ id: `tree-${node.path}`,
1274
+ paddingX: 1,
1275
+ height: 1,
1276
+ backgroundColor: isCursor ? "#1a1a1a" : void 0,
1277
+ onMouseDown: () => {
1278
+ setCursorIndex(i);
1279
+ toggleFolder(node.path);
1280
+ },
1281
+ children: /* @__PURE__ */ jsxs4("text", { children: [
1282
+ /* @__PURE__ */ jsx4("span", { fg: "#555555", children: indent }),
1283
+ /* @__PURE__ */ jsxs4("span", { fg: isCursor ? "#cccccc" : "#888888", children: [
1284
+ chevron,
1285
+ " ",
1286
+ displayName2
1287
+ ] })
1288
+ ] })
1289
+ },
1290
+ `folder-${node.path}`
1291
+ );
1292
+ }
1293
+ const isSelectedFile = node.fileIndex === safeFileIndex;
1294
+ const added = node.added ?? 0;
1295
+ const removed = node.removed ?? 0;
1296
+ const addedPart = added > 0 ? `+${added}` : "";
1297
+ const removedPart = removed > 0 ? `-${removed}` : "";
1298
+ const bubbleColor = added > 0 && removed > 0 ? "#ffaa00" : removed > 0 ? "#ff4444" : "#66bb6a";
1299
+ const overhead = indent.length + 2 + statsColumnWidth + 1;
1300
+ const nameMax = Math.max(1, usable - overhead);
1301
+ const displayName = truncateName(node.name, nameMax);
1302
+ const bg = isCursor ? "#0d2b0d" : isSelectedFile ? "#0a1a0a" : void 0;
1303
+ return /* @__PURE__ */ jsxs4(
1304
+ "box",
1305
+ {
1306
+ id: `tree-${node.path}`,
1307
+ paddingX: 1,
1308
+ height: 1,
1309
+ backgroundColor: bg,
1310
+ onMouseDown: () => selectFile(node.fileIndex, i),
1311
+ flexDirection: "row",
1312
+ justifyContent: "space-between",
1313
+ children: [
1314
+ /* @__PURE__ */ jsxs4("text", { children: [
1315
+ /* @__PURE__ */ jsx4("span", { fg: "#555555", children: indent }),
1316
+ /* @__PURE__ */ jsxs4("span", { fg: bubbleColor, children: [
1317
+ "\u25CF",
1318
+ " "
1319
+ ] }),
1320
+ /* @__PURE__ */ jsx4("span", { fg: isSelectedFile || isCursor ? "#66bb6a" : "#cccccc", children: displayName })
1321
+ ] }),
1322
+ /* @__PURE__ */ jsx4("box", { width: statsColumnWidth, flexShrink: 0, justifyContent: "flex-end", flexDirection: "row", children: /* @__PURE__ */ jsxs4("text", { children: [
1323
+ addedPart && /* @__PURE__ */ jsx4("span", { fg: "#66bb6a", children: addedPart }),
1324
+ addedPart && removedPart && /* @__PURE__ */ jsx4("span", { fg: "#444444", children: " " }),
1325
+ removedPart && /* @__PURE__ */ jsx4("span", { fg: "#ff4444", children: removedPart })
1326
+ ] }) })
1327
+ ]
1328
+ },
1329
+ `file-${node.path}`
1330
+ );
1331
+ }) })
1332
+ ]
1333
+ }
1334
+ ),
1335
+ /* @__PURE__ */ jsxs4("box", { flexGrow: 1, flexDirection: "column", children: [
1336
+ /* @__PURE__ */ jsxs4("box", { paddingX: 1, flexDirection: "column", flexShrink: 0, children: [
1337
+ /* @__PURE__ */ jsxs4("box", { flexDirection: "row", justifyContent: "space-between", height: 1, children: [
1338
+ /* @__PURE__ */ jsxs4("text", { children: [
1339
+ /* @__PURE__ */ jsx4("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx4("strong", { children: repoName }) }),
1340
+ totalAdded > 0 && /* @__PURE__ */ jsxs4("span", { fg: "#66bb6a", children: [
1341
+ " +",
1342
+ totalAdded
1343
+ ] }),
1344
+ totalAdded > 0 && totalRemoved > 0 && /* @__PURE__ */ jsx4("span", { fg: "#444444", children: " " }),
1345
+ totalRemoved > 0 && /* @__PURE__ */ jsxs4("span", { fg: "#ff4444", children: [
1346
+ "-",
1347
+ totalRemoved
1348
+ ] })
1349
+ ] }),
1350
+ /* @__PURE__ */ jsxs4("text", { fg: "#555555", children: [
1351
+ safeFileIndex + 1,
1352
+ "/",
1353
+ files.length
1354
+ ] })
1355
+ ] }),
1356
+ /* @__PURE__ */ jsx4("box", { flexDirection: "row", height: 1, backgroundColor: "#0d0d0d", paddingX: 1, children: /* @__PURE__ */ jsxs4("text", { children: [
1357
+ /* @__PURE__ */ jsx4("span", { fg: diffPaneActive ? "#66bb6a" : "#444444", children: diffPaneActive ? "\u25B8 " : " " }),
1358
+ (() => {
1359
+ const lastSlash = selected.path.lastIndexOf("/");
1360
+ const dir = lastSlash >= 0 ? selected.path.slice(0, lastSlash + 1) : "";
1361
+ const base = lastSlash >= 0 ? selected.path.slice(lastSlash + 1) : selected.path;
1362
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
1363
+ dir && /* @__PURE__ */ jsx4("span", { fg: "#666666", children: dir }),
1364
+ /* @__PURE__ */ jsx4("span", { fg: diffPaneActive ? "#66bb6a" : "#ffffff", children: /* @__PURE__ */ jsx4("strong", { children: base }) })
1365
+ ] });
1366
+ })()
1367
+ ] }) })
1368
+ ] }),
1369
+ /* @__PURE__ */ jsx4("scrollbox", { ref: diffScrollRef, flexGrow: 1, focused: focused && innerFocus === "diff", children: /* @__PURE__ */ jsx4("box", { paddingX: 1, children: /* @__PURE__ */ jsx4(DiffView, { diff: selected.diff, filePath: selected.path }) }) })
1370
+ ] })
1371
+ ] });
1372
+ }
1373
+
1374
+ // src/interactive/components/StructuredUserMessage.tsx
1375
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
878
1376
  function SourceBadge({ source }) {
879
1377
  const config = SOURCE_CONFIG[source];
880
- return /* @__PURE__ */ jsx4("text", { children: /* @__PURE__ */ jsx4("span", { fg: "#000000", bg: config.color, children: ` ${config.label} ` }) });
1378
+ return /* @__PURE__ */ jsx5("text", { children: /* @__PURE__ */ jsx5("span", { fg: "#000000", bg: config.color, children: ` ${config.label} ` }) });
881
1379
  }
882
1380
  function MetadataPill({ label, value }) {
883
- return /* @__PURE__ */ jsx4("text", { children: label ? /* @__PURE__ */ jsxs4(Fragment, { children: [
884
- /* @__PURE__ */ jsxs4("span", { fg: "#888888", children: [
1381
+ return /* @__PURE__ */ jsx5("text", { children: label ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1382
+ /* @__PURE__ */ jsxs5("span", { fg: "#888888", children: [
885
1383
  label,
886
1384
  ": "
887
1385
  ] }),
888
- /* @__PURE__ */ jsx4("span", { fg: "#cccccc", children: value })
889
- ] }) : /* @__PURE__ */ jsx4("span", { fg: "#cccccc", children: value }) });
1386
+ /* @__PURE__ */ jsx5("span", { fg: "#cccccc", children: value })
1387
+ ] }) : /* @__PURE__ */ jsx5("span", { fg: "#cccccc", children: value }) });
890
1388
  }
891
1389
  function MessageBody({ children }) {
892
1390
  if (!children) return null;
893
- return /* @__PURE__ */ jsx4("text", { fg: "#ffffff", selectable: true, children });
1391
+ return /* @__PURE__ */ jsx5("text", { fg: "#ffffff", selectable: true, children });
894
1392
  }
895
1393
  function ExpandableSection({ label, children }) {
896
- const [expanded, setExpanded] = useState3(false);
1394
+ const [expanded, setExpanded] = useState4(false);
897
1395
  if (!children) return null;
898
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", children: [
899
- /* @__PURE__ */ jsx4("box", { onMouseDown: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxs4("text", { children: [
900
- /* @__PURE__ */ jsxs4("span", { fg: "#555555", children: [
1396
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
1397
+ /* @__PURE__ */ jsx5("box", { onMouseDown: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxs5("text", { children: [
1398
+ /* @__PURE__ */ jsxs5("span", { fg: "#555555", children: [
901
1399
  expanded ? "\u25BE" : "\u25B8",
902
1400
  " "
903
1401
  ] }),
904
- /* @__PURE__ */ jsx4("span", { fg: "#888888", children: label })
1402
+ /* @__PURE__ */ jsx5("span", { fg: "#888888", children: label })
905
1403
  ] }) }),
906
- expanded && /* @__PURE__ */ jsx4("box", { paddingLeft: 2, children: /* @__PURE__ */ jsx4("text", { fg: "#888888", selectable: true, children }) })
1404
+ expanded && /* @__PURE__ */ jsx5("box", { paddingLeft: 2, children: /* @__PURE__ */ jsx5("text", { fg: "#888888", selectable: true, children }) })
1405
+ ] });
1406
+ }
1407
+ function ExpandableDiffSection({ label, diffContent, filePath }) {
1408
+ const [expanded, setExpanded] = useState4(false);
1409
+ if (!diffContent) return null;
1410
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
1411
+ /* @__PURE__ */ jsx5("box", { onMouseDown: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxs5("text", { children: [
1412
+ /* @__PURE__ */ jsxs5("span", { fg: "#555555", children: [
1413
+ expanded ? "\u25BE" : "\u25B8",
1414
+ " "
1415
+ ] }),
1416
+ /* @__PURE__ */ jsx5("span", { fg: "#888888", children: label })
1417
+ ] }) }),
1418
+ expanded && /* @__PURE__ */ jsx5("box", { paddingLeft: 1, children: /* @__PURE__ */ jsx5(DiffView, { diff: diffContent, filePath }) })
907
1419
  ] });
908
1420
  }
909
1421
  function CIFailureMessage({ data }) {
910
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", gap: 0, children: [
911
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", gap: 1, children: [
912
- /* @__PURE__ */ jsx4(SourceBadge, { source: "ci_failure" }),
913
- /* @__PURE__ */ jsx4(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
914
- /* @__PURE__ */ jsx4(MetadataPill, { label: "Workflow", value: data.workflowName }),
915
- /* @__PURE__ */ jsx4(MetadataPill, { label: "Status", value: data.conclusion })
1422
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1423
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1424
+ /* @__PURE__ */ jsx5(SourceBadge, { source: "ci_failure" }),
1425
+ /* @__PURE__ */ jsx5(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
1426
+ /* @__PURE__ */ jsx5(MetadataPill, { label: "Workflow", value: data.workflowName }),
1427
+ /* @__PURE__ */ jsx5(MetadataPill, { label: "Status", value: data.conclusion })
916
1428
  ] }),
917
- /* @__PURE__ */ jsxs4("text", { fg: "#ffffff", children: [
1429
+ /* @__PURE__ */ jsxs5("text", { fg: "#ffffff", children: [
918
1430
  "Workflow ",
919
- /* @__PURE__ */ jsx4("span", { fg: "#ff4444", children: data.conclusionText }),
1431
+ /* @__PURE__ */ jsx5("span", { fg: "#ff4444", children: data.conclusionText }),
920
1432
  " on PR #" + data.prNumber
921
1433
  ] }),
922
- data.workflowFile && /* @__PURE__ */ jsx4(MetadataPill, { label: "File", value: data.workflowFile }),
923
- data.runUrl && /* @__PURE__ */ jsx4(MetadataPill, { label: "Run", value: data.runUrl })
1434
+ data.workflowFile && /* @__PURE__ */ jsx5(MetadataPill, { label: "File", value: data.workflowFile }),
1435
+ data.runUrl && /* @__PURE__ */ jsx5(MetadataPill, { label: "Run", value: data.runUrl })
924
1436
  ] });
925
1437
  }
926
1438
  function LinearIssueMessage({ data }) {
927
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", gap: 0, children: [
928
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", gap: 1, children: [
929
- /* @__PURE__ */ jsx4(SourceBadge, { source: "linear_issue" }),
930
- /* @__PURE__ */ jsx4(MetadataPill, { label: "", value: data.identifier })
1439
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1440
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1441
+ /* @__PURE__ */ jsx5(SourceBadge, { source: "linear_issue" }),
1442
+ /* @__PURE__ */ jsx5(MetadataPill, { label: "", value: data.identifier })
931
1443
  ] }),
932
- /* @__PURE__ */ jsx4("text", { fg: "#ffffff", children: /* @__PURE__ */ jsx4("strong", { children: data.title }) }),
933
- data.description && data.description !== "No description provided." && /* @__PURE__ */ jsx4(MessageBody, { children: data.description }),
934
- /* @__PURE__ */ jsx4(ExpandableSection, { label: "Additional context", children: data.additionalContext }),
935
- data.parentIssue && /* @__PURE__ */ jsx4(ExpandableSection, { label: `Parent: ${data.parentIssue.identifier} - ${data.parentIssue.title}`, children: data.parentIssue.description })
1444
+ /* @__PURE__ */ jsx5("text", { fg: "#ffffff", children: /* @__PURE__ */ jsx5("strong", { children: data.title }) }),
1445
+ data.description && data.description !== "No description provided." && /* @__PURE__ */ jsx5(MessageBody, { children: data.description }),
1446
+ /* @__PURE__ */ jsx5(ExpandableSection, { label: "Additional context", children: data.additionalContext }),
1447
+ data.parentIssue && /* @__PURE__ */ jsx5(ExpandableSection, { label: `Parent: ${data.parentIssue.identifier} - ${data.parentIssue.title}`, children: data.parentIssue.description })
936
1448
  ] });
937
1449
  }
938
1450
  function GitHubIssueNewMessage({ data }) {
939
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", gap: 0, children: [
940
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", gap: 1, children: [
941
- /* @__PURE__ */ jsx4(SourceBadge, { source: "github_issue_new" }),
942
- /* @__PURE__ */ jsx4(MetadataPill, { label: "Issue", value: `#${data.issueNumber}` }),
943
- data.triggeringUser && /* @__PURE__ */ jsx4(MetadataPill, { label: "From", value: `@${data.triggeringUser}` })
1451
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1452
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1453
+ /* @__PURE__ */ jsx5(SourceBadge, { source: "github_issue_new" }),
1454
+ /* @__PURE__ */ jsx5(MetadataPill, { label: "Issue", value: `#${data.issueNumber}` }),
1455
+ data.triggeringUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.triggeringUser}` })
944
1456
  ] }),
945
- /* @__PURE__ */ jsx4("text", { fg: "#ffffff", children: /* @__PURE__ */ jsx4("strong", { children: data.issueTitle }) }),
946
- data.triggeringComment && /* @__PURE__ */ jsx4(MessageBody, { children: data.triggeringComment }),
947
- /* @__PURE__ */ jsx4(ExpandableSection, { label: "Issue description", children: data.issueDescription })
1457
+ /* @__PURE__ */ jsx5("text", { fg: "#ffffff", children: /* @__PURE__ */ jsx5("strong", { children: data.issueTitle }) }),
1458
+ data.triggeringComment && /* @__PURE__ */ jsx5(MessageBody, { children: data.triggeringComment }),
1459
+ /* @__PURE__ */ jsx5(ExpandableSection, { label: "Issue description", children: data.issueDescription })
948
1460
  ] });
949
1461
  }
950
1462
  function GitHubIssueExistingMessage({ data }) {
951
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", gap: 0, children: [
952
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", gap: 1, children: [
953
- /* @__PURE__ */ jsx4(SourceBadge, { source: "github_issue_existing" }),
954
- /* @__PURE__ */ jsx4(MetadataPill, { label: "Issue", value: `#${data.issueNumber}` }),
955
- data.commentUser && /* @__PURE__ */ jsx4(MetadataPill, { label: "From", value: `@${data.commentUser}` })
1463
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1464
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1465
+ /* @__PURE__ */ jsx5(SourceBadge, { source: "github_issue_existing" }),
1466
+ /* @__PURE__ */ jsx5(MetadataPill, { label: "Issue", value: `#${data.issueNumber}` }),
1467
+ data.commentUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.commentUser}` })
956
1468
  ] }),
957
- /* @__PURE__ */ jsx4(MessageBody, { children: data.commentBody })
1469
+ /* @__PURE__ */ jsx5(MessageBody, { children: data.commentBody })
958
1470
  ] });
959
1471
  }
960
1472
  function GitHubPRNewMessage({ data }) {
961
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", gap: 0, children: [
962
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", gap: 1, children: [
963
- /* @__PURE__ */ jsx4(SourceBadge, { source: "github_pr_new" }),
964
- /* @__PURE__ */ jsx4(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
965
- data.mentioningUser && /* @__PURE__ */ jsx4(MetadataPill, { label: "From", value: `@${data.mentioningUser}` })
1473
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1474
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1475
+ /* @__PURE__ */ jsx5(SourceBadge, { source: "github_pr_new" }),
1476
+ /* @__PURE__ */ jsx5(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
1477
+ data.mentioningUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.mentioningUser}` })
966
1478
  ] }),
967
- /* @__PURE__ */ jsx4(MessageBody, { children: data.contextMessage })
1479
+ /* @__PURE__ */ jsx5(MessageBody, { children: data.contextMessage })
968
1480
  ] });
969
1481
  }
970
1482
  function GitHubPRCodeReviewMessage({ data }) {
971
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", gap: 0, children: [
972
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", gap: 1, children: [
973
- /* @__PURE__ */ jsx4(SourceBadge, { source: "github_pr_existing_review" }),
974
- /* @__PURE__ */ jsx4(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
975
- data.commentUser && /* @__PURE__ */ jsx4(MetadataPill, { label: "From", value: `@${data.commentUser}` }),
976
- data.filePath && /* @__PURE__ */ jsx4(MetadataPill, { label: "File", value: data.filePath })
1483
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1484
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1485
+ /* @__PURE__ */ jsx5(SourceBadge, { source: "github_pr_existing_review" }),
1486
+ /* @__PURE__ */ jsx5(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
1487
+ data.commentUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.commentUser}` }),
1488
+ data.filePath && /* @__PURE__ */ jsx5(MetadataPill, { label: "File", value: data.filePath })
977
1489
  ] }),
978
- /* @__PURE__ */ jsx4(MessageBody, { children: data.commentBody }),
979
- /* @__PURE__ */ jsx4(ExpandableSection, { label: "Diff context", children: data.diffHunk })
1490
+ /* @__PURE__ */ jsx5(MessageBody, { children: data.commentBody }),
1491
+ /* @__PURE__ */ jsx5(ExpandableDiffSection, { label: "Diff context", diffContent: data.diffHunk, filePath: data.filePath })
980
1492
  ] });
981
1493
  }
982
1494
  function GitHubPRReviewMessage({ data }) {
983
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", gap: 0, children: [
984
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", gap: 1, children: [
985
- /* @__PURE__ */ jsx4(SourceBadge, { source: "github_pr_existing_pr_review" }),
986
- /* @__PURE__ */ jsx4(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
987
- data.reviewUser && /* @__PURE__ */ jsx4(MetadataPill, { label: "From", value: `@${data.reviewUser}` })
1495
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1496
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1497
+ /* @__PURE__ */ jsx5(SourceBadge, { source: "github_pr_existing_pr_review" }),
1498
+ /* @__PURE__ */ jsx5(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
1499
+ data.reviewUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.reviewUser}` })
988
1500
  ] }),
989
- /* @__PURE__ */ jsxs4("text", { fg: "#888888", children: [
1501
+ /* @__PURE__ */ jsxs5("text", { fg: "#888888", children: [
990
1502
  "@" + data.reviewUser + " ",
991
- /* @__PURE__ */ jsx4("span", { fg: "#ffffff", children: data.reviewAction })
1503
+ /* @__PURE__ */ jsx5("span", { fg: "#ffffff", children: data.reviewAction })
992
1504
  ] }),
993
- data.commentBody && /* @__PURE__ */ jsx4(MessageBody, { children: data.commentBody })
1505
+ data.commentBody && /* @__PURE__ */ jsx5(MessageBody, { children: data.commentBody })
994
1506
  ] });
995
1507
  }
996
1508
  function GitHubPRGeneralMessage({ data }) {
997
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", gap: 0, children: [
998
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", gap: 1, children: [
999
- /* @__PURE__ */ jsx4(SourceBadge, { source: "github_pr_existing_general" }),
1000
- /* @__PURE__ */ jsx4(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
1001
- data.commentUser && /* @__PURE__ */ jsx4(MetadataPill, { label: "From", value: `@${data.commentUser}` })
1509
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1510
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1511
+ /* @__PURE__ */ jsx5(SourceBadge, { source: "github_pr_existing_general" }),
1512
+ /* @__PURE__ */ jsx5(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
1513
+ data.commentUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.commentUser}` })
1002
1514
  ] }),
1003
- /* @__PURE__ */ jsx4(MessageBody, { children: data.commentBody })
1515
+ /* @__PURE__ */ jsx5(MessageBody, { children: data.commentBody })
1004
1516
  ] });
1005
1517
  }
1006
1518
  function SlackTaskMessage({ data }) {
1007
1519
  const hasDistinctContext = data.threadContext && data.threadContext !== `@${data.latestMessageUser}: ${data.latestMessageText}`;
1008
- return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", gap: 0, children: [
1009
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", gap: 1, children: [
1010
- /* @__PURE__ */ jsx4(SourceBadge, { source: "slack_task" }),
1011
- data.workspaceTarget && /* @__PURE__ */ jsx4(MetadataPill, { label: "Target", value: data.workspaceTarget }),
1012
- data.latestMessageUser && /* @__PURE__ */ jsx4(MetadataPill, { label: "From", value: `@${data.latestMessageUser}` })
1520
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1521
+ /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1522
+ /* @__PURE__ */ jsx5(SourceBadge, { source: "slack_task" }),
1523
+ data.workspaceTarget && /* @__PURE__ */ jsx5(MetadataPill, { label: "Target", value: data.workspaceTarget }),
1524
+ data.latestMessageUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.latestMessageUser}` })
1013
1525
  ] }),
1014
- /* @__PURE__ */ jsx4(MessageBody, { children: data.latestMessageText }),
1015
- hasDistinctContext && /* @__PURE__ */ jsx4(ExpandableSection, { label: "Thread context", children: data.threadContext })
1526
+ /* @__PURE__ */ jsx5(MessageBody, { children: data.latestMessageText }),
1527
+ hasDistinctContext && /* @__PURE__ */ jsx5(ExpandableSection, { label: "Thread context", children: data.threadContext })
1016
1528
  ] });
1017
1529
  }
1018
1530
  function StructuredUserMessage({ parsed }) {
1019
1531
  switch (parsed.source) {
1020
1532
  case "ci_failure":
1021
- return /* @__PURE__ */ jsx4(CIFailureMessage, { data: parsed });
1533
+ return /* @__PURE__ */ jsx5(CIFailureMessage, { data: parsed });
1022
1534
  case "linear_issue":
1023
- return /* @__PURE__ */ jsx4(LinearIssueMessage, { data: parsed });
1535
+ return /* @__PURE__ */ jsx5(LinearIssueMessage, { data: parsed });
1024
1536
  case "github_issue_new":
1025
- return /* @__PURE__ */ jsx4(GitHubIssueNewMessage, { data: parsed });
1537
+ return /* @__PURE__ */ jsx5(GitHubIssueNewMessage, { data: parsed });
1026
1538
  case "github_issue_existing":
1027
- return /* @__PURE__ */ jsx4(GitHubIssueExistingMessage, { data: parsed });
1539
+ return /* @__PURE__ */ jsx5(GitHubIssueExistingMessage, { data: parsed });
1028
1540
  case "github_pr_new":
1029
- return /* @__PURE__ */ jsx4(GitHubPRNewMessage, { data: parsed });
1541
+ return /* @__PURE__ */ jsx5(GitHubPRNewMessage, { data: parsed });
1030
1542
  case "github_pr_existing_review":
1031
- return /* @__PURE__ */ jsx4(GitHubPRCodeReviewMessage, { data: parsed });
1543
+ return /* @__PURE__ */ jsx5(GitHubPRCodeReviewMessage, { data: parsed });
1032
1544
  case "github_pr_existing_pr_review":
1033
- return /* @__PURE__ */ jsx4(GitHubPRReviewMessage, { data: parsed });
1545
+ return /* @__PURE__ */ jsx5(GitHubPRReviewMessage, { data: parsed });
1034
1546
  case "github_pr_existing_general":
1035
- return /* @__PURE__ */ jsx4(GitHubPRGeneralMessage, { data: parsed });
1547
+ return /* @__PURE__ */ jsx5(GitHubPRGeneralMessage, { data: parsed });
1036
1548
  case "slack_task":
1037
- return /* @__PURE__ */ jsx4(SlackTaskMessage, { data: parsed });
1549
+ return /* @__PURE__ */ jsx5(SlackTaskMessage, { data: parsed });
1038
1550
  default: {
1039
1551
  const _exhaustive = parsed;
1040
1552
  throw new Error(`Unhandled prompt source: ${_exhaustive.source}`);
@@ -1043,36 +1555,7 @@ function StructuredUserMessage({ parsed }) {
1043
1555
  }
1044
1556
 
1045
1557
  // src/interactive/components/ChatMessage.tsx
1046
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
1047
- var sharedSyntaxStyle = null;
1048
- function getSyntaxStyle() {
1049
- if (!sharedSyntaxStyle) {
1050
- sharedSyntaxStyle = SyntaxStyle.fromTheme([
1051
- // Markdown text styling
1052
- { scope: ["markup.heading", "markup.heading.1", "markup.heading.2", "markup.heading.3"], style: { foreground: "#66bb6a", bold: true } },
1053
- { scope: ["markup.bold", "markup.strong"], style: { foreground: "#ffffff", bold: true } },
1054
- { scope: ["markup.italic", "markup.emphasis"], style: { foreground: "#e0e0e0", italic: true } },
1055
- { scope: ["markup.link"], style: { foreground: "#7dcfff", underline: true } },
1056
- { scope: ["markup.link.url"], style: { foreground: "#7dcfff", underline: true } },
1057
- { scope: ["markup.link.text"], style: { foreground: "#66bb6a", underline: true } },
1058
- { scope: ["markup.link.label"], style: { foreground: "#66bb6a", underline: true } },
1059
- { scope: ["markup.list"], style: { foreground: "#66bb6a" } },
1060
- { scope: ["markup.quote"], style: { foreground: "#888888", italic: true } },
1061
- { scope: ["markup.raw", "markup.raw.block"], style: { foreground: "#bb9af7" } },
1062
- { scope: ["markup.raw.inline"], style: { foreground: "#bb9af7", background: "#1a1a2e" } },
1063
- // Code syntax highlighting
1064
- { scope: ["keyword", "keyword.control", "keyword.operator"], style: { foreground: "#bb9af7" } },
1065
- { scope: ["string", "string.quoted"], style: { foreground: "#9ece6a" } },
1066
- { scope: ["comment", "comment.line", "comment.block"], style: { foreground: "#555555", italic: true } },
1067
- { scope: ["constant", "constant.numeric", "constant.language"], style: { foreground: "#ff9e64" } },
1068
- { scope: ["entity.name.function", "support.function"], style: { foreground: "#7aa2f7" } },
1069
- { scope: ["entity.name.type", "support.type"], style: { foreground: "#2ac3de" } },
1070
- { scope: ["variable", "variable.other"], style: { foreground: "#c0caf5" } },
1071
- { scope: ["punctuation"], style: { foreground: "#888888" } }
1072
- ]);
1073
- }
1074
- return sharedSyntaxStyle;
1075
- }
1558
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
1076
1559
  var AGENT_LABELS = {
1077
1560
  claude: "Claude",
1078
1561
  codex: "Codex"
@@ -1081,15 +1564,15 @@ function truncate2(text, maxLen) {
1081
1564
  return text.length > maxLen ? text.slice(0, maxLen - 1) + "\u2026" : text;
1082
1565
  }
1083
1566
  function StatusIcon({ status }) {
1084
- if (status === "completed") return /* @__PURE__ */ jsxs5("span", { fg: "#66bb6a", children: [
1567
+ if (status === "completed") return /* @__PURE__ */ jsxs6("span", { fg: "#66bb6a", children: [
1085
1568
  " ",
1086
1569
  "\u2713"
1087
1570
  ] });
1088
- if (status === "failed") return /* @__PURE__ */ jsxs5("span", { fg: "#ff4444", children: [
1571
+ if (status === "failed") return /* @__PURE__ */ jsxs6("span", { fg: "#ff4444", children: [
1089
1572
  " ",
1090
1573
  "\u2717"
1091
1574
  ] });
1092
- if (status === "in_progress") return /* @__PURE__ */ jsxs5("span", { fg: "#ffaa00", children: [
1575
+ if (status === "in_progress") return /* @__PURE__ */ jsxs6("span", { fg: "#ffaa00", children: [
1093
1576
  " ",
1094
1577
  "\u2026"
1095
1578
  ] });
@@ -1103,7 +1586,7 @@ function ActionLine({
1103
1586
  onToggle,
1104
1587
  expanded
1105
1588
  }) {
1106
- return /* @__PURE__ */ jsxs5(
1589
+ return /* @__PURE__ */ jsxs6(
1107
1590
  "box",
1108
1591
  {
1109
1592
  paddingX: 1,
@@ -1111,15 +1594,15 @@ function ActionLine({
1111
1594
  justifyContent: "space-between",
1112
1595
  onMouseDown: onToggle,
1113
1596
  children: [
1114
- /* @__PURE__ */ jsxs5("text", { children: [
1115
- /* @__PURE__ */ jsx5("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx5("strong", { children: label }) }),
1116
- /* @__PURE__ */ jsxs5("span", { fg: "#cccccc", children: [
1597
+ /* @__PURE__ */ jsxs6("text", { children: [
1598
+ /* @__PURE__ */ jsx6("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx6("strong", { children: label }) }),
1599
+ /* @__PURE__ */ jsxs6("span", { fg: "#cccccc", children: [
1117
1600
  " ",
1118
1601
  arg.replace(/\n/g, " ").replace(/\s+/g, " ")
1119
1602
  ] }),
1120
- /* @__PURE__ */ jsx5(StatusIcon, { status })
1603
+ /* @__PURE__ */ jsx6(StatusIcon, { status })
1121
1604
  ] }),
1122
- expandable && /* @__PURE__ */ jsx5("text", { children: /* @__PURE__ */ jsx5("span", { fg: "#555555", children: expanded ? "\u25BE" : "\u25B8" }) })
1605
+ expandable && /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#555555", children: expanded ? "\u25BE" : "\u25B8" }) })
1123
1606
  ]
1124
1607
  }
1125
1608
  );
@@ -1130,9 +1613,9 @@ function ExpandableAction({
1130
1613
  status,
1131
1614
  children
1132
1615
  }) {
1133
- const [expanded, setExpanded] = useState4(false);
1134
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
1135
- /* @__PURE__ */ jsx5(
1616
+ const [expanded, setExpanded] = useState5(false);
1617
+ return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", children: [
1618
+ /* @__PURE__ */ jsx6(
1136
1619
  ActionLine,
1137
1620
  {
1138
1621
  label,
@@ -1146,12 +1629,59 @@ function ExpandableAction({
1146
1629
  expanded && children
1147
1630
  ] });
1148
1631
  }
1632
+ var CHANGE_STYLE = {
1633
+ add: { color: "#66bb6a", sign: "+" },
1634
+ delete: { color: "#ff4444", sign: "-" }
1635
+ };
1636
+ var DEFAULT_CHANGE_STYLE = { color: "#ffaa00", sign: "~" };
1637
+ function getChangeStyle(action) {
1638
+ return CHANGE_STYLE[action] ?? DEFAULT_CHANGE_STYLE;
1639
+ }
1640
+ function PatchOperation({ op, defaultExpanded = false }) {
1641
+ const [expanded, setExpanded] = useState5(defaultExpanded);
1642
+ const { color, sign } = getChangeStyle(op.action);
1643
+ const stats = useMemo4(() => op.diff ? countDiffStats(op.diff) : null, [op.diff]);
1644
+ if (!op.diff) {
1645
+ return /* @__PURE__ */ jsxs6("text", { children: [
1646
+ /* @__PURE__ */ jsx6("span", { fg: color, children: sign }),
1647
+ /* @__PURE__ */ jsxs6("span", { fg: "#cccccc", children: [
1648
+ " ",
1649
+ op.path
1650
+ ] })
1651
+ ] });
1652
+ }
1653
+ return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", children: [
1654
+ /* @__PURE__ */ jsxs6("box", { onMouseDown: () => setExpanded((e) => !e), flexDirection: "row", justifyContent: "space-between", children: [
1655
+ /* @__PURE__ */ jsxs6("text", { children: [
1656
+ /* @__PURE__ */ jsx6("span", { fg: color, children: sign }),
1657
+ /* @__PURE__ */ jsxs6("span", { fg: "#cccccc", children: [
1658
+ " ",
1659
+ op.path
1660
+ ] }),
1661
+ stats && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1662
+ /* @__PURE__ */ jsx6("span", { fg: "#444444", children: " " }),
1663
+ stats.removed > 0 && /* @__PURE__ */ jsxs6("span", { fg: "#ff4444", children: [
1664
+ "-",
1665
+ stats.removed
1666
+ ] }),
1667
+ stats.removed > 0 && stats.added > 0 && /* @__PURE__ */ jsx6("span", { fg: "#444444", children: " " }),
1668
+ stats.added > 0 && /* @__PURE__ */ jsxs6("span", { fg: "#66bb6a", children: [
1669
+ "+",
1670
+ stats.added
1671
+ ] })
1672
+ ] })
1673
+ ] }),
1674
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#555555", children: expanded ? "\u25BE" : "\u25B8" }) })
1675
+ ] }),
1676
+ expanded && /* @__PURE__ */ jsx6("box", { paddingLeft: 1, children: /* @__PURE__ */ jsx6(DiffView, { diff: op.diff, filePath: op.path }) })
1677
+ ] });
1678
+ }
1149
1679
  function isStructuredPrompt(p) {
1150
1680
  return p.source !== "raw";
1151
1681
  }
1152
1682
  function UserMessageContent({ content }) {
1153
- const parsed = useMemo3(() => parseUserMessage(content), [content]);
1154
- return /* @__PURE__ */ jsx5(
1683
+ const parsed = useMemo4(() => parseUserMessage(content), [content]);
1684
+ return /* @__PURE__ */ jsx6(
1155
1685
  "box",
1156
1686
  {
1157
1687
  flexDirection: "column",
@@ -1159,9 +1689,9 @@ function UserMessageContent({ content }) {
1159
1689
  paddingX: 2,
1160
1690
  paddingY: 1,
1161
1691
  marginX: 1,
1162
- children: isStructuredPrompt(parsed) ? /* @__PURE__ */ jsx5(StructuredUserMessage, { parsed }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
1163
- /* @__PURE__ */ jsx5("text", { children: /* @__PURE__ */ jsx5("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx5("strong", { children: "You" }) }) }),
1164
- /* @__PURE__ */ jsx5("text", { fg: "#ffffff", selectable: true, children: content })
1692
+ children: isStructuredPrompt(parsed) ? /* @__PURE__ */ jsx6(StructuredUserMessage, { parsed }) : /* @__PURE__ */ jsxs6(Fragment3, { children: [
1693
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx6("strong", { children: "You" }) }) }),
1694
+ /* @__PURE__ */ jsx6("text", { fg: "#ffffff", selectable: true, children: content })
1165
1695
  ] })
1166
1696
  }
1167
1697
  );
@@ -1170,14 +1700,14 @@ function ChatMessage({ message, provider }) {
1170
1700
  switch (message.type) {
1171
1701
  case "user": {
1172
1702
  if (message.content === "Request interrupted") {
1173
- return /* @__PURE__ */ jsx5("box", { paddingX: 1, justifyContent: "center", children: /* @__PURE__ */ jsx5("text", { fg: "#555555", children: "--- Request interrupted ---" }) });
1703
+ return /* @__PURE__ */ jsx6("box", { paddingX: 1, justifyContent: "center", children: /* @__PURE__ */ jsx6("text", { fg: "#555555", children: "--- Request interrupted ---" }) });
1174
1704
  }
1175
- return /* @__PURE__ */ jsx5(UserMessageContent, { content: message.content });
1705
+ return /* @__PURE__ */ jsx6(UserMessageContent, { content: message.content });
1176
1706
  }
1177
1707
  case "agent":
1178
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", paddingX: 1, children: [
1179
- /* @__PURE__ */ jsx5("text", { children: /* @__PURE__ */ jsx5("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx5("strong", { children: AGENT_LABELS[provider] }) }) }),
1180
- /* @__PURE__ */ jsx5(
1708
+ return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", paddingX: 1, children: [
1709
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx6("strong", { children: AGENT_LABELS[provider] }) }) }),
1710
+ /* @__PURE__ */ jsx6(
1181
1711
  "markdown",
1182
1712
  {
1183
1713
  content: message.content,
@@ -1190,57 +1720,54 @@ function ChatMessage({ message, provider }) {
1190
1720
  )
1191
1721
  ] });
1192
1722
  case "reasoning":
1193
- return /* @__PURE__ */ jsx5("box", { paddingX: 1, children: /* @__PURE__ */ jsx5(SpinnerLabel, { color: "#555555", label: "thinking" }) });
1723
+ return /* @__PURE__ */ jsx6("box", { paddingX: 1, children: /* @__PURE__ */ jsx6(SpinnerLabel, { color: "#555555", label: "thinking" }) });
1194
1724
  case "command": {
1195
1725
  const cmdArg = truncate2(message.command, 60);
1196
1726
  const exitSuffix = message.exitCode !== void 0 && message.exitCode !== 0 ? ` (exit ${message.exitCode})` : "";
1197
1727
  if (!message.output) {
1198
- return /* @__PURE__ */ jsx5(ActionLine, { label: "Command", arg: cmdArg + exitSuffix, status: message.status });
1728
+ return /* @__PURE__ */ jsx6(ActionLine, { label: "Command", arg: cmdArg + exitSuffix, status: message.status });
1199
1729
  }
1200
- return /* @__PURE__ */ jsx5(ExpandableAction, { label: "Command", arg: cmdArg + exitSuffix, status: message.status, children: /* @__PURE__ */ jsx5("box", { paddingLeft: 2, paddingX: 1, children: /* @__PURE__ */ jsx5("text", { fg: "#888888", selectable: true, children: message.output.length > 500 ? message.output.slice(0, 500) + "\n\u2026" : message.output }) }) });
1730
+ return /* @__PURE__ */ jsx6(ExpandableAction, { label: "Command", arg: cmdArg + exitSuffix, status: message.status, children: /* @__PURE__ */ jsx6("box", { paddingLeft: 2, paddingX: 1, children: /* @__PURE__ */ jsx6("text", { fg: "#888888", selectable: true, children: message.output.length > 500 ? message.output.slice(0, 500) + "\n\u2026" : message.output }) }) });
1201
1731
  }
1202
1732
  case "file_change": {
1203
1733
  const count = message.changes.length;
1204
- return /* @__PURE__ */ jsx5(ExpandableAction, { label: "Files", arg: `${count}`, status: message.status, children: /* @__PURE__ */ jsx5("box", { flexDirection: "column", paddingLeft: 2, paddingX: 1, children: message.changes.map((change, i) => /* @__PURE__ */ jsxs5("text", { children: [
1205
- /* @__PURE__ */ jsx5(
1206
- "span",
1207
- {
1208
- fg: change.kind === "add" ? "#66bb6a" : change.kind === "delete" ? "#ff4444" : "#ffaa00",
1209
- children: change.kind === "add" ? "+" : change.kind === "delete" ? "-" : "~"
1210
- }
1211
- ),
1212
- /* @__PURE__ */ jsxs5("span", { fg: "#cccccc", children: [
1213
- " ",
1214
- change.path
1215
- ] })
1216
- ] }, i)) }) });
1734
+ return /* @__PURE__ */ jsx6(ExpandableAction, { label: "Files", arg: `${count}`, status: message.status, children: /* @__PURE__ */ jsx6("box", { flexDirection: "column", paddingLeft: 2, paddingX: 1, children: message.changes.map((change, i) => {
1735
+ const { color, sign } = getChangeStyle(change.kind);
1736
+ return /* @__PURE__ */ jsxs6("text", { children: [
1737
+ /* @__PURE__ */ jsx6("span", { fg: color, children: sign }),
1738
+ /* @__PURE__ */ jsxs6("span", { fg: "#cccccc", children: [
1739
+ " ",
1740
+ change.path
1741
+ ] })
1742
+ ] }, i);
1743
+ }) }) });
1217
1744
  }
1218
1745
  case "tool_call": {
1219
1746
  const toolArg = truncate2(message.tool, 50);
1220
1747
  if (message.output || message.input) {
1221
- return /* @__PURE__ */ jsx5(ExpandableAction, { label: "Tool", arg: toolArg, status: message.status, children: /* @__PURE__ */ jsxs5("box", { flexDirection: "column", paddingLeft: 2, paddingX: 1, children: [
1222
- message.input && /* @__PURE__ */ jsx5("text", { fg: "#888888", selectable: true, children: typeof message.input === "string" ? truncate2(message.input, 300) : truncate2(JSON.stringify(message.input, null, 2), 300) }),
1223
- message.output && /* @__PURE__ */ jsx5("text", { fg: "#888888", selectable: true, children: truncate2(message.output, 300) })
1748
+ return /* @__PURE__ */ jsx6(ExpandableAction, { label: "Tool", arg: toolArg, status: message.status, children: /* @__PURE__ */ jsxs6("box", { flexDirection: "column", paddingLeft: 2, paddingX: 1, children: [
1749
+ message.input && /* @__PURE__ */ jsx6("text", { fg: "#888888", selectable: true, children: typeof message.input === "string" ? truncate2(message.input, 300) : truncate2(JSON.stringify(message.input, null, 2), 300) }),
1750
+ message.output && /* @__PURE__ */ jsx6("text", { fg: "#888888", selectable: true, children: truncate2(message.output, 300) })
1224
1751
  ] }) });
1225
1752
  }
1226
- return /* @__PURE__ */ jsx5(ActionLine, { label: "Tool", arg: toolArg, status: message.status });
1753
+ return /* @__PURE__ */ jsx6(ActionLine, { label: "Tool", arg: toolArg, status: message.status });
1227
1754
  }
1228
1755
  case "web_search":
1229
- return /* @__PURE__ */ jsx5(ActionLine, { label: "Search", arg: truncate2(message.query, 50), status: message.status });
1756
+ return /* @__PURE__ */ jsx6(ActionLine, { label: "Search", arg: truncate2(message.query, 50), status: message.status });
1230
1757
  case "todo_list": {
1231
1758
  const completed = message.items.filter((i) => i.completed).length;
1232
1759
  const total = message.items.length;
1233
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", paddingX: 1, children: [
1234
- /* @__PURE__ */ jsx5("text", { children: /* @__PURE__ */ jsx5("span", { fg: "#888888", children: /* @__PURE__ */ jsxs5("strong", { children: [
1760
+ return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", paddingX: 1, children: [
1761
+ /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#888888", children: /* @__PURE__ */ jsxs6("strong", { children: [
1235
1762
  "Plan (",
1236
1763
  completed,
1237
1764
  "/",
1238
1765
  total,
1239
1766
  ")"
1240
1767
  ] }) }) }),
1241
- message.items.map((item, i) => /* @__PURE__ */ jsxs5("text", { children: [
1242
- /* @__PURE__ */ jsx5("span", { fg: item.completed ? "#66bb6a" : "#555555", children: item.completed ? " \u2611" : " \u2610" }),
1243
- /* @__PURE__ */ jsxs5("span", { fg: "#ffffff", children: [
1768
+ message.items.map((item, i) => /* @__PURE__ */ jsxs6("text", { children: [
1769
+ /* @__PURE__ */ jsx6("span", { fg: item.completed ? "#66bb6a" : "#555555", children: item.completed ? " \u2611" : " \u2610" }),
1770
+ /* @__PURE__ */ jsxs6("span", { fg: "#ffffff", children: [
1244
1771
  " ",
1245
1772
  item.text
1246
1773
  ] })
@@ -1248,26 +1775,15 @@ function ChatMessage({ message, provider }) {
1248
1775
  ] });
1249
1776
  }
1250
1777
  case "subagent":
1251
- return /* @__PURE__ */ jsx5(ActionLine, { label: "Agent", arg: truncate2(message.description, 50), status: message.status });
1778
+ return /* @__PURE__ */ jsx6(ActionLine, { label: "Agent", arg: truncate2(message.description, 50), status: message.status });
1252
1779
  case "error":
1253
- return /* @__PURE__ */ jsx5("box", { paddingX: 1, children: /* @__PURE__ */ jsx5("text", { fg: "#ff4444", selectable: true, children: message.message }) });
1780
+ return /* @__PURE__ */ jsx6("box", { paddingX: 1, children: /* @__PURE__ */ jsx6("text", { fg: "#ff4444", selectable: true, children: message.message }) });
1254
1781
  case "skill":
1255
- return /* @__PURE__ */ jsx5(ActionLine, { label: "Skill", arg: message.skillName + (message.args ? ` ${message.args}` : ""), status: message.status });
1782
+ return /* @__PURE__ */ jsx6(ActionLine, { label: "Skill", arg: message.skillName + (message.args ? ` ${message.args}` : ""), status: message.status });
1256
1783
  case "patch": {
1257
1784
  const opCount = message.operations.length;
1258
- return /* @__PURE__ */ jsx5(ExpandableAction, { label: "Patch", arg: `${opCount}`, children: /* @__PURE__ */ jsx5("box", { flexDirection: "column", paddingLeft: 2, paddingX: 1, children: message.operations.map((op, i) => /* @__PURE__ */ jsxs5("text", { children: [
1259
- /* @__PURE__ */ jsx5(
1260
- "span",
1261
- {
1262
- fg: op.action === "add" ? "#66bb6a" : op.action === "delete" ? "#ff4444" : "#ffaa00",
1263
- children: op.action === "add" ? "+" : op.action === "delete" ? "-" : "~"
1264
- }
1265
- ),
1266
- /* @__PURE__ */ jsxs5("span", { fg: "#cccccc", children: [
1267
- " ",
1268
- op.path
1269
- ] })
1270
- ] }, i)) }) });
1785
+ const autoExpand = opCount === 1 && !!message.operations[0].diff;
1786
+ return /* @__PURE__ */ jsx6(ExpandableAction, { label: "Patch", arg: `${opCount}`, children: /* @__PURE__ */ jsx6("box", { flexDirection: "column", paddingLeft: 2, paddingX: 1, children: message.operations.map((op, i) => /* @__PURE__ */ jsx6(PatchOperation, { op, defaultExpanded: autoExpand }, i)) }) });
1271
1787
  }
1272
1788
  default:
1273
1789
  return null;
@@ -1275,7 +1791,7 @@ function ChatMessage({ message, provider }) {
1275
1791
  }
1276
1792
 
1277
1793
  // src/interactive/components/ChatArea.tsx
1278
- import { jsx as jsx6, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
1794
+ import { jsx as jsx7, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
1279
1795
  var textareaKeyBindings = [
1280
1796
  { name: "return", action: "submit" },
1281
1797
  { name: "return", shift: true, action: "newline" },
@@ -1298,10 +1814,10 @@ function ChatArea({
1298
1814
  isProcessing,
1299
1815
  loading
1300
1816
  }) {
1301
- const textareaRef = useRef(null);
1302
- const scrollboxRef = useRef(null);
1303
- const isAtBottomRef = useRef(true);
1304
- const onSendMessageRef = useRef(onSendMessage);
1817
+ const textareaRef = useRef2(null);
1818
+ const scrollboxRef = useRef2(null);
1819
+ const isAtBottomRef = useRef2(true);
1820
+ const onSendMessageRef = useRef2(onSendMessage);
1305
1821
  onSendMessageRef.current = onSendMessage;
1306
1822
  const inputFocused = focusPanel === "chat-input";
1307
1823
  const tabsFocused = focusPanel === "chat-tabs";
@@ -1343,20 +1859,7 @@ function ChatArea({
1343
1859
  textarea.onSubmit = void 0;
1344
1860
  };
1345
1861
  }, []);
1346
- useKeyboard2((key) => {
1347
- if (!historyFocused) return;
1348
- const scrollbox = scrollboxRef.current;
1349
- if (!scrollbox) return;
1350
- if (key.name === "j" || key.name === "down") {
1351
- scrollbox.scrollBy(3);
1352
- return;
1353
- }
1354
- if (key.name === "k" || key.name === "up") {
1355
- scrollbox.scrollBy(-3);
1356
- return;
1357
- }
1358
- });
1359
- useKeyboard2((key) => {
1862
+ useKeyboard3((key) => {
1360
1863
  if (!tabsFocused || chats.length <= 1 || !selectedChatId) return;
1361
1864
  if (key.name === "left" || key.name === "right" || key.name === "tab" && !key.shift) {
1362
1865
  const currentIdx = chats.findIndex((c) => c.id === selectedChatId);
@@ -1366,7 +1869,7 @@ function ChatArea({
1366
1869
  }
1367
1870
  }
1368
1871
  });
1369
- return /* @__PURE__ */ jsxs6(
1872
+ return /* @__PURE__ */ jsxs7(
1370
1873
  "box",
1371
1874
  {
1372
1875
  flexGrow: 1,
@@ -1378,7 +1881,7 @@ function ChatArea({
1378
1881
  flexDirection: "column",
1379
1882
  backgroundColor: "#000000",
1380
1883
  children: [
1381
- chats.length > 1 && /* @__PURE__ */ jsx6(
1884
+ chats.length > 1 && /* @__PURE__ */ jsx7(
1382
1885
  "box",
1383
1886
  {
1384
1887
  height: 3,
@@ -1394,7 +1897,7 @@ function ChatArea({
1394
1897
  children: chats.map((chat) => {
1395
1898
  const isSelected = chat.id === selectedChatId;
1396
1899
  const label = `${chat.title}${chat.processing ? " ..." : ""}`;
1397
- return /* @__PURE__ */ jsx6(
1900
+ return /* @__PURE__ */ jsx7(
1398
1901
  "box",
1399
1902
  {
1400
1903
  backgroundColor: isSelected ? "#1a1a1a" : void 0,
@@ -1403,14 +1906,14 @@ function ChatArea({
1403
1906
  onSelectChat(chat.id);
1404
1907
  onFocus("chat-tabs");
1405
1908
  },
1406
- children: /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: isSelected ? modeColors.text : "#666666", children: label }) })
1909
+ children: /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: isSelected ? modeColors.text : "#666666", children: label }) })
1407
1910
  },
1408
1911
  chat.id
1409
1912
  );
1410
1913
  })
1411
1914
  }
1412
1915
  ),
1413
- /* @__PURE__ */ jsx6(
1916
+ /* @__PURE__ */ jsx7(
1414
1917
  "scrollbox",
1415
1918
  {
1416
1919
  ref: scrollboxRef,
@@ -1419,22 +1922,22 @@ function ChatArea({
1419
1922
  paddingX: 1,
1420
1923
  paddingY: 1,
1421
1924
  onMouseDown: () => onFocus("chat-history"),
1422
- children: loading ? /* @__PURE__ */ jsx6("box", { paddingX: 1, children: /* @__PURE__ */ jsx6("text", { fg: "#666666", children: "Loading messages..." }) }) : displayMessages.length === 0 ? /* @__PURE__ */ jsx6("box", { paddingX: 1, children: /* @__PURE__ */ jsx6("text", { fg: "#666666", children: "No messages yet. Send a message to start chatting." }) }) : (() => {
1925
+ children: loading ? /* @__PURE__ */ jsx7("box", { paddingX: 1, children: /* @__PURE__ */ jsx7("text", { fg: "#666666", children: "Loading messages..." }) }) : displayMessages.length === 0 ? /* @__PURE__ */ jsx7("box", { paddingX: 1, children: /* @__PURE__ */ jsx7("text", { fg: "#666666", children: "No messages yet. Send a message to start chatting." }) }) : (() => {
1423
1926
  const activeChat = chats.find((c) => c.id === selectedChatId);
1424
1927
  const provider = activeChat?.provider ?? "claude";
1425
1928
  const primaryTypes = /* @__PURE__ */ new Set(["user", "agent", "todo_list"]);
1426
1929
  return displayMessages.map((msg, i) => {
1427
1930
  const prev = i > 0 ? displayMessages[i - 1] : null;
1428
1931
  const needsSpacing = prev && (primaryTypes.has(msg.type) || primaryTypes.has(prev.type));
1429
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", children: [
1430
- needsSpacing && /* @__PURE__ */ jsx6("box", { height: 1 }),
1431
- /* @__PURE__ */ jsx6(ChatMessage, { message: msg, provider })
1932
+ return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", children: [
1933
+ needsSpacing && /* @__PURE__ */ jsx7("box", { height: 1 }),
1934
+ /* @__PURE__ */ jsx7(ChatMessage, { message: msg, provider })
1432
1935
  ] }, msg.id || `msg-${i}`);
1433
1936
  });
1434
1937
  })()
1435
1938
  }
1436
1939
  ),
1437
- /* @__PURE__ */ jsxs6(
1940
+ /* @__PURE__ */ jsxs7(
1438
1941
  "box",
1439
1942
  {
1440
1943
  flexDirection: "column",
@@ -1444,7 +1947,7 @@ function ChatArea({
1444
1947
  backgroundColor: "#111111",
1445
1948
  onMouseDown: () => onFocus("chat-input"),
1446
1949
  children: [
1447
- /* @__PURE__ */ jsx6("box", { height: 6, paddingX: 1, children: /* @__PURE__ */ jsx6(
1950
+ /* @__PURE__ */ jsx7("box", { height: 6, paddingX: 1, children: /* @__PURE__ */ jsx7(
1448
1951
  "textarea",
1449
1952
  {
1450
1953
  ref: textareaRef,
@@ -1459,7 +1962,7 @@ function ChatArea({
1459
1962
  height: 5
1460
1963
  }
1461
1964
  ) }),
1462
- /* @__PURE__ */ jsxs6(
1965
+ /* @__PURE__ */ jsxs7(
1463
1966
  "box",
1464
1967
  {
1465
1968
  height: 1,
@@ -1467,8 +1970,8 @@ function ChatArea({
1467
1970
  flexDirection: "row",
1468
1971
  justifyContent: "space-between",
1469
1972
  children: [
1470
- /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: modeColors.text, children: taskMode === "build" ? "Build" : "Plan" }) }),
1471
- isProcessing && /* @__PURE__ */ jsx6(SpinnerLabel, { color: "#ffaa00", label: "thinking" })
1973
+ /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: modeColors.text, children: taskMode === "build" ? "Build" : "Plan" }) }),
1974
+ isProcessing && /* @__PURE__ */ jsx7(SpinnerLabel, { color: "#ffaa00", label: "thinking" })
1472
1975
  ]
1473
1976
  }
1474
1977
  )
@@ -1481,10 +1984,10 @@ function ChatArea({
1481
1984
  }
1482
1985
 
1483
1986
  // src/interactive/components/WorkspaceInfo.tsx
1484
- import React5, { useState as useState5, useMemo as useMemo4, useCallback as useCallback4 } from "react";
1987
+ import React6, { useState as useState6, useMemo as useMemo5, useCallback as useCallback5, useRef as useRef3 } from "react";
1485
1988
  import open from "open";
1486
- import { useKeyboard as useKeyboard3 } from "@opentui/react";
1487
- import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
1989
+ import { useKeyboard as useKeyboard4 } from "@opentui/react";
1990
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
1488
1991
  var WEB_APP_URL = process.env.REPLICAS_WEB_URL || "https://replicas.dev";
1489
1992
  function buildInteractiveItems(workspaceId, status, previews, wakingWorkspaceId) {
1490
1993
  const items = [];
@@ -1499,6 +2002,19 @@ function buildInteractiveItems(workspaceId, status, previews, wakingWorkspaceId)
1499
2002
  if (workspaceId && status.status === "sleeping" && wakingWorkspaceId !== workspaceId) {
1500
2003
  items.push({ type: "wake", workspaceId });
1501
2004
  }
2005
+ const repoDiffs = [];
2006
+ if (status.repoStatuses) {
2007
+ for (const repo of status.repoStatuses) {
2008
+ if (repo.gitDiff?.fullDiff && (repo.gitDiff.added > 0 || repo.gitDiff.removed > 0)) {
2009
+ repoDiffs.push({ type: "diff", repoName: repo.name, diff: repo.gitDiff.fullDiff, added: repo.gitDiff.added, removed: repo.gitDiff.removed });
2010
+ }
2011
+ }
2012
+ }
2013
+ items.push({ type: "view-chat" });
2014
+ if (repoDiffs.length > 0) {
2015
+ items.push({ type: "view-diff" });
2016
+ items.push(...repoDiffs);
2017
+ }
1502
2018
  for (const preview of previews) {
1503
2019
  if (preview.publicUrl) {
1504
2020
  items.push({ type: "preview", url: preview.publicUrl, port: preview.port });
@@ -1523,10 +2039,36 @@ function getItemLabel(item) {
1523
2039
  return `\u2197 Preview :${item.port}`;
1524
2040
  case "pr":
1525
2041
  return `\u2197 View PR (${item.repoName})`;
2042
+ case "diff":
2043
+ return `\u25B8 Diff +${item.added} -${item.removed}`;
2044
+ case "view-chat":
2045
+ return "1 Chat";
2046
+ case "view-diff":
2047
+ return "2 Diff";
1526
2048
  case "createPr":
1527
2049
  return "+ Create PR";
1528
2050
  }
1529
2051
  }
2052
+ function getItemId(item) {
2053
+ switch (item.type) {
2054
+ case "dashboard":
2055
+ return "info-dashboard";
2056
+ case "wake":
2057
+ return "info-wake";
2058
+ case "preview":
2059
+ return `info-preview-${item.port}`;
2060
+ case "pr":
2061
+ return `info-pr-${item.repoName}`;
2062
+ case "diff":
2063
+ return `info-diff-${item.repoName}`;
2064
+ case "view-chat":
2065
+ return "info-view-chat";
2066
+ case "view-diff":
2067
+ return "info-view-diff";
2068
+ case "createPr":
2069
+ return "info-create-pr";
2070
+ }
2071
+ }
1530
2072
  var AUTH_METHOD_LABELS = {
1531
2073
  oauth: "OAuth",
1532
2074
  api_key: "API Key",
@@ -1535,24 +2077,24 @@ var AUTH_METHOD_LABELS = {
1535
2077
  };
1536
2078
  function StatusDot({ status }) {
1537
2079
  if (status === true || status === "yes") {
1538
- return /* @__PURE__ */ jsx7("span", { fg: "#66bb6a", children: "\u2713" });
2080
+ return /* @__PURE__ */ jsx8("span", { fg: "#66bb6a", children: "\u2713" });
1539
2081
  }
1540
2082
  if (status === false || status === "no") {
1541
- return /* @__PURE__ */ jsx7("span", { fg: "#ff4444", children: "\u2717" });
2083
+ return /* @__PURE__ */ jsx8("span", { fg: "#ff4444", children: "\u2717" });
1542
2084
  }
1543
- return /* @__PURE__ */ jsx7("span", { fg: "#555555", children: "-" });
2085
+ return /* @__PURE__ */ jsx8("span", { fg: "#555555", children: "-" });
1544
2086
  }
1545
2087
  function SectionLabel({ title }) {
1546
- return /* @__PURE__ */ jsx7("box", { backgroundColor: "#151515", paddingX: 1, children: /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: "#666666", children: /* @__PURE__ */ jsx7("strong", { children: title.toUpperCase() }) }) }) });
2088
+ return /* @__PURE__ */ jsx8("box", { backgroundColor: "#151515", paddingX: 1, children: /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#666666", children: /* @__PURE__ */ jsx8("strong", { children: title.toUpperCase() }) }) }) });
1547
2089
  }
1548
2090
  function Section({ title, children }) {
1549
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", marginBottom: 1, children: [
1550
- /* @__PURE__ */ jsx7(SectionLabel, { title }),
1551
- /* @__PURE__ */ jsx7("box", { flexDirection: "column", backgroundColor: "#0a0a0a", children })
2091
+ return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", marginBottom: 1, children: [
2092
+ /* @__PURE__ */ jsx8(SectionLabel, { title }),
2093
+ /* @__PURE__ */ jsx8("box", { flexDirection: "column", backgroundColor: "#0a0a0a", children })
1552
2094
  ] });
1553
2095
  }
1554
2096
  function CardItem({ label, status }) {
1555
- return /* @__PURE__ */ jsxs7(
2097
+ return /* @__PURE__ */ jsxs8(
1556
2098
  "box",
1557
2099
  {
1558
2100
  flexDirection: "row",
@@ -1561,8 +2103,8 @@ function CardItem({ label, status }) {
1561
2103
  backgroundColor: "#111111",
1562
2104
  marginBottom: 0,
1563
2105
  children: [
1564
- /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: "#cccccc", children: label }) }),
1565
- /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7(StatusDot, { status }) })
2106
+ /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#cccccc", children: label }) }),
2107
+ /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8(StatusDot, { status }) })
1566
2108
  ]
1567
2109
  }
1568
2110
  );
@@ -1575,40 +2117,77 @@ function DetailList({
1575
2117
  const actualSet = new Set(actual);
1576
2118
  const rows = uniqueExpected.length > 0 ? uniqueExpected : Array.from(actualSet).sort((a, b) => a.localeCompare(b));
1577
2119
  if (rows.length === 0) return null;
1578
- return /* @__PURE__ */ jsx7(Fragment3, { children: rows.map((item, i) => /* @__PURE__ */ jsx7(CardItem, { label: item, status: actualSet.has(item) }, i)) });
2120
+ return /* @__PURE__ */ jsx8(Fragment4, { children: rows.map((item, i) => /* @__PURE__ */ jsx8(CardItem, { label: item, status: actualSet.has(item) }, i)) });
1579
2121
  }
1580
- function InteractiveRow({ label, highlighted, disabled, onClick }) {
2122
+ function InteractiveRow({ id, label, highlighted, disabled, onClick }) {
1581
2123
  if (disabled) {
1582
- return /* @__PURE__ */ jsx7("box", { paddingX: 1, backgroundColor: "#111111", children: /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: "#444444", children: label }) }) });
2124
+ return /* @__PURE__ */ jsx8("box", { id, paddingX: 1, backgroundColor: "#111111", children: /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#444444", children: label }) }) });
1583
2125
  }
1584
- return /* @__PURE__ */ jsx7(
2126
+ return /* @__PURE__ */ jsx8(
1585
2127
  "box",
1586
2128
  {
2129
+ id,
1587
2130
  paddingX: 1,
1588
2131
  backgroundColor: highlighted ? "#1a2a1a" : "#111111",
1589
2132
  onMouseDown: onClick,
1590
- children: /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: highlighted ? "#66bb6a" : "#7dcfff", children: label }) })
2133
+ children: /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: highlighted ? "#66bb6a" : "#7dcfff", children: label }) })
1591
2134
  }
1592
2135
  );
1593
2136
  }
1594
- function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, envConfig, previews, onWakeWorkspace, onCreatePr, isPlanMode, wakingWorkspaceId }) {
2137
+ function ViewModeRow({
2138
+ id,
2139
+ label,
2140
+ active,
2141
+ highlighted,
2142
+ disabled,
2143
+ onClick
2144
+ }) {
2145
+ const bg = highlighted ? "#1a2a1a" : "#111111";
2146
+ const fg = disabled ? "#444444" : active ? "#66bb6a" : "#cccccc";
2147
+ const marker = active ? "\u25B8" : " ";
2148
+ return /* @__PURE__ */ jsx8(
2149
+ "box",
2150
+ {
2151
+ id,
2152
+ paddingX: 1,
2153
+ backgroundColor: bg,
2154
+ onMouseDown: disabled ? void 0 : onClick,
2155
+ children: /* @__PURE__ */ jsxs8("text", { children: [
2156
+ /* @__PURE__ */ jsxs8("span", { fg: active ? "#66bb6a" : "#555555", children: [
2157
+ marker,
2158
+ " "
2159
+ ] }),
2160
+ active ? /* @__PURE__ */ jsx8("span", { fg, children: /* @__PURE__ */ jsx8("strong", { children: label }) }) : /* @__PURE__ */ jsx8("span", { fg, children: label })
2161
+ ] })
2162
+ }
2163
+ );
2164
+ }
2165
+ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, envConfig, previews, onWakeWorkspace, onViewDiff, wakingWorkspaceId, viewMode, viewingDiffRepoName, onSelectChatMode, onSelectDiffMode, onCreatePr, isPlanMode }) {
1595
2166
  const borderColor = focused ? "#66bb6a" : "#333333";
1596
- const [cursorIndex, setCursorIndex] = useState5(0);
1597
- const interactiveItems = useMemo4(() => {
2167
+ const [cursorIndex, setCursorIndex] = useState6(0);
2168
+ const interactiveItems = useMemo5(() => {
1598
2169
  if (!status || !workspaceName) return [];
1599
2170
  return buildInteractiveItems(workspaceId, status, previews, wakingWorkspaceId);
1600
2171
  }, [workspaceId, workspaceName, status, previews, wakingWorkspaceId]);
2172
+ const diffItems = useMemo5(
2173
+ () => interactiveItems.filter((item) => item.type === "diff"),
2174
+ [interactiveItems]
2175
+ );
2176
+ const hasAnyDiff = diffItems.length > 0;
1601
2177
  const safeCursor = interactiveItems.length > 0 ? Math.min(cursorIndex, interactiveItems.length - 1) : 0;
1602
- const moveCursor = useCallback4(
2178
+ const scrollboxRef = useRef3(null);
2179
+ const moveCursor = useCallback5(
1603
2180
  (next) => {
1604
2181
  const len = interactiveItems.length;
1605
2182
  if (len === 0) return;
1606
2183
  const wrapped = (next % len + len) % len;
1607
2184
  setCursorIndex(wrapped);
2185
+ const item = interactiveItems[wrapped];
2186
+ if (item) scrollboxRef.current?.scrollChildIntoView(getItemId(item));
1608
2187
  },
1609
- [interactiveItems.length]
2188
+ [interactiveItems]
1610
2189
  );
1611
- const handleAction = useCallback4(
2190
+ const handleAction = useCallback5(
1612
2191
  (item) => {
1613
2192
  if (!item) return;
1614
2193
  switch (item.type) {
@@ -1627,6 +2206,15 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1627
2206
  open(item.url).catch(() => {
1628
2207
  });
1629
2208
  break;
2209
+ case "diff":
2210
+ onViewDiff?.(item.diff, item.repoName);
2211
+ break;
2212
+ case "view-chat":
2213
+ onSelectChatMode();
2214
+ break;
2215
+ case "view-diff":
2216
+ onSelectDiffMode();
2217
+ break;
1630
2218
  case "createPr":
1631
2219
  if (!isPlanMode) onCreatePr();
1632
2220
  break;
@@ -1634,7 +2222,7 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1634
2222
  },
1635
2223
  [onWakeWorkspace, onCreatePr]
1636
2224
  );
1637
- useKeyboard3((key) => {
2225
+ useKeyboard4((key) => {
1638
2226
  if (!focused) return;
1639
2227
  if (interactiveItems.length === 0) return;
1640
2228
  if (key.name === "j" || key.name === "down") {
@@ -1668,7 +2256,7 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1668
2256
  return;
1669
2257
  }
1670
2258
  });
1671
- const isHighlighted = useCallback4(
2259
+ const isHighlighted = useCallback5(
1672
2260
  (item) => {
1673
2261
  if (!focused) return false;
1674
2262
  const idx = interactiveItems.indexOf(item);
@@ -1676,19 +2264,20 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1676
2264
  },
1677
2265
  [focused, interactiveItems, safeCursor]
1678
2266
  );
1679
- const findItem = useCallback4(
2267
+ const findItem = useCallback5(
1680
2268
  (type, key) => {
1681
2269
  return interactiveItems.find((item) => {
1682
2270
  if (item.type !== type) return false;
1683
2271
  if (type === "preview" && key) return item.port === Number(key);
1684
2272
  if (type === "pr" && key) return item.repoName === key;
2273
+ if (type === "diff" && key) return item.repoName === key;
1685
2274
  return true;
1686
2275
  });
1687
2276
  },
1688
2277
  [interactiveItems]
1689
2278
  );
1690
2279
  if (!workspaceName) {
1691
- return /* @__PURE__ */ jsx7(
2280
+ return /* @__PURE__ */ jsx8(
1692
2281
  "box",
1693
2282
  {
1694
2283
  width: 30,
@@ -1700,12 +2289,12 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1700
2289
  flexDirection: "column",
1701
2290
  paddingX: 1,
1702
2291
  backgroundColor: "#000000",
1703
- children: /* @__PURE__ */ jsx7("text", { fg: "#666666", children: "Select a workspace" })
2292
+ children: /* @__PURE__ */ jsx8("text", { fg: "#666666", children: "Select a workspace" })
1704
2293
  }
1705
2294
  );
1706
2295
  }
1707
2296
  if (loading || !status) {
1708
- return /* @__PURE__ */ jsx7(
2297
+ return /* @__PURE__ */ jsx8(
1709
2298
  "box",
1710
2299
  {
1711
2300
  width: 30,
@@ -1717,7 +2306,7 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1717
2306
  flexDirection: "column",
1718
2307
  paddingX: 1,
1719
2308
  backgroundColor: "#000000",
1720
- children: /* @__PURE__ */ jsx7("text", { fg: "#666666", children: "Loading..." })
2309
+ children: /* @__PURE__ */ jsx8("text", { fg: "#666666", children: "Loading..." })
1721
2310
  }
1722
2311
  );
1723
2312
  }
@@ -1729,7 +2318,7 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1729
2318
  const dashboardItem = findItem("dashboard");
1730
2319
  const wakeItem = findItem("wake");
1731
2320
  const createPrItem = findItem("createPr");
1732
- return /* @__PURE__ */ jsx7(
2321
+ return /* @__PURE__ */ jsx8(
1733
2322
  "box",
1734
2323
  {
1735
2324
  width: 30,
@@ -1740,16 +2329,16 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1740
2329
  titleAlignment: "center",
1741
2330
  flexDirection: "column",
1742
2331
  backgroundColor: "#000000",
1743
- children: /* @__PURE__ */ jsxs7("scrollbox", { focused, flexGrow: 1, children: [
1744
- /* @__PURE__ */ jsx7("box", { backgroundColor: "#111111", paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ jsxs7("box", { flexDirection: "column", children: [
1745
- /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx7("strong", { children: workspaceName }) }) }),
1746
- /* @__PURE__ */ jsxs7("text", { children: [
1747
- /* @__PURE__ */ jsxs7("span", { fg: statusColor, children: [
2332
+ children: /* @__PURE__ */ jsxs8("scrollbox", { ref: scrollboxRef, focused: false, flexGrow: 1, children: [
2333
+ /* @__PURE__ */ jsx8("box", { backgroundColor: "#111111", paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ jsxs8("box", { flexDirection: "column", children: [
2334
+ /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx8("strong", { children: workspaceName }) }) }),
2335
+ /* @__PURE__ */ jsxs8("text", { children: [
2336
+ /* @__PURE__ */ jsxs8("span", { fg: statusColor, children: [
1748
2337
  "\u25CF",
1749
2338
  " ",
1750
2339
  status.status
1751
2340
  ] }),
1752
- env && /* @__PURE__ */ jsxs7("span", { fg: "#555555", children: [
2341
+ env && /* @__PURE__ */ jsxs8("span", { fg: "#555555", children: [
1753
2342
  " ",
1754
2343
  "\u2502",
1755
2344
  " ",
@@ -1757,103 +2346,158 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1757
2346
  ] })
1758
2347
  ] })
1759
2348
  ] }) }),
1760
- createPrItem && /* @__PURE__ */ jsx7(
2349
+ createPrItem && /* @__PURE__ */ jsx8(
1761
2350
  InteractiveRow,
1762
2351
  {
2352
+ id: getItemId(createPrItem),
1763
2353
  label: getItemLabel(createPrItem),
1764
2354
  highlighted: isHighlighted(createPrItem),
1765
2355
  disabled: isPlanMode,
1766
2356
  onClick: () => handleAction(createPrItem)
1767
2357
  }
1768
2358
  ),
1769
- dashboardItem && /* @__PURE__ */ jsx7(
2359
+ dashboardItem && /* @__PURE__ */ jsx8(
1770
2360
  InteractiveRow,
1771
2361
  {
2362
+ id: getItemId(dashboardItem),
1772
2363
  label: getItemLabel(dashboardItem),
1773
2364
  highlighted: isHighlighted(dashboardItem),
1774
2365
  onClick: () => handleAction(dashboardItem)
1775
2366
  }
1776
2367
  ),
1777
- wakeItem && /* @__PURE__ */ jsx7(
2368
+ wakeItem && /* @__PURE__ */ jsx8(
1778
2369
  InteractiveRow,
1779
2370
  {
2371
+ id: getItemId(wakeItem),
1780
2372
  label: getItemLabel(wakeItem),
1781
2373
  highlighted: isHighlighted(wakeItem),
1782
2374
  onClick: () => handleAction(wakeItem)
1783
2375
  }
1784
2376
  ),
1785
- (status.isClaudeProcessing || status.isCodexProcessing) && /* @__PURE__ */ jsxs7("box", { backgroundColor: "#1a1500", paddingX: 1, marginX: 1, marginBottom: 1, children: [
1786
- status.isClaudeProcessing && /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsxs7("span", { fg: "#ffaa00", children: [
2377
+ (status.isClaudeProcessing || status.isCodexProcessing) && /* @__PURE__ */ jsxs8("box", { backgroundColor: "#1a1500", paddingX: 1, marginX: 1, marginBottom: 1, children: [
2378
+ status.isClaudeProcessing && /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsxs8("span", { fg: "#ffaa00", children: [
1787
2379
  "\u25C6",
1788
2380
  " Claude thinking..."
1789
2381
  ] }) }),
1790
- status.isCodexProcessing && /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsxs7("span", { fg: "#ffaa00", children: [
2382
+ status.isCodexProcessing && /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsxs8("span", { fg: "#ffaa00", children: [
1791
2383
  "\u25C6",
1792
2384
  " Codex thinking..."
1793
2385
  ] }) })
1794
2386
  ] }),
1795
- env && /* @__PURE__ */ jsxs7(Section, { title: "Agents", children: [
1796
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
1797
- /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: "#cccccc", children: "Claude" }) }),
1798
- /* @__PURE__ */ jsx7("text", { children: AUTH_METHOD_LABELS[env.claudeAuthMethod] ? /* @__PURE__ */ jsx7("span", { fg: "#66bb6a", children: AUTH_METHOD_LABELS[env.claudeAuthMethod] }) : /* @__PURE__ */ jsx7("span", { fg: "#ff4444", children: "\u2717" }) })
2387
+ env && /* @__PURE__ */ jsxs8(Section, { title: "Agents", children: [
2388
+ /* @__PURE__ */ jsxs8("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
2389
+ /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#cccccc", children: "Claude" }) }),
2390
+ /* @__PURE__ */ jsx8("text", { children: AUTH_METHOD_LABELS[env.claudeAuthMethod] ? /* @__PURE__ */ jsx8("span", { fg: "#66bb6a", children: AUTH_METHOD_LABELS[env.claudeAuthMethod] }) : /* @__PURE__ */ jsx8("span", { fg: "#ff4444", children: "\u2717" }) })
1799
2391
  ] }),
1800
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
1801
- /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: "#cccccc", children: "Codex" }) }),
1802
- /* @__PURE__ */ jsx7("text", { children: AUTH_METHOD_LABELS[env.codexAuthMethod] ? /* @__PURE__ */ jsx7("span", { fg: "#66bb6a", children: AUTH_METHOD_LABELS[env.codexAuthMethod] }) : /* @__PURE__ */ jsx7("span", { fg: "#ff4444", children: "\u2717" }) })
2392
+ /* @__PURE__ */ jsxs8("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
2393
+ /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#cccccc", children: "Codex" }) }),
2394
+ /* @__PURE__ */ jsx8("text", { children: AUTH_METHOD_LABELS[env.codexAuthMethod] ? /* @__PURE__ */ jsx8("span", { fg: "#66bb6a", children: AUTH_METHOD_LABELS[env.codexAuthMethod] }) : /* @__PURE__ */ jsx8("span", { fg: "#ff4444", children: "\u2717" }) })
1803
2395
  ] })
1804
2396
  ] }),
1805
- env && /* @__PURE__ */ jsxs7(Section, { title: "Integrations", children: [
1806
- /* @__PURE__ */ jsx7(CardItem, { label: "GitHub", status: env.githubAccessConfigured }),
1807
- /* @__PURE__ */ jsx7(CardItem, { label: "Slack", status: env.slackAccessConfigured }),
1808
- /* @__PURE__ */ jsx7(CardItem, { label: "Linear", status: env.linearAccessConfigured })
2397
+ /* @__PURE__ */ jsx8(Section, { title: "View", children: (() => {
2398
+ const chatItem = findItem("view-chat");
2399
+ const diffItem = findItem("view-diff");
2400
+ return /* @__PURE__ */ jsxs8(Fragment4, { children: [
2401
+ chatItem && /* @__PURE__ */ jsx8(
2402
+ ViewModeRow,
2403
+ {
2404
+ id: getItemId(chatItem),
2405
+ label: "1 Chat",
2406
+ active: viewMode === "chat",
2407
+ highlighted: isHighlighted(chatItem),
2408
+ onClick: () => handleAction(chatItem)
2409
+ }
2410
+ ),
2411
+ diffItem ? /* @__PURE__ */ jsx8(
2412
+ ViewModeRow,
2413
+ {
2414
+ id: getItemId(diffItem),
2415
+ label: "2 Diff",
2416
+ active: viewMode === "diff",
2417
+ highlighted: isHighlighted(diffItem),
2418
+ onClick: () => handleAction(diffItem)
2419
+ }
2420
+ ) : /* @__PURE__ */ jsx8(ViewModeRow, { label: "2 Diff", active: false, highlighted: false, disabled: true, onClick: () => {
2421
+ } }),
2422
+ hasAnyDiff && diffItems.map((item, i) => {
2423
+ const isActive = viewMode === "diff" && viewingDiffRepoName === item.repoName;
2424
+ const cursorOn = isHighlighted(item);
2425
+ const bg = cursorOn ? "#1a2a1a" : isActive ? "#0d2b0d" : "#111111";
2426
+ const nameColor = isActive || cursorOn ? "#66bb6a" : "#cccccc";
2427
+ return /* @__PURE__ */ jsxs8(
2428
+ "box",
2429
+ {
2430
+ id: getItemId(item),
2431
+ paddingX: 1,
2432
+ backgroundColor: bg,
2433
+ onMouseDown: () => handleAction(item),
2434
+ flexDirection: "row",
2435
+ justifyContent: "space-between",
2436
+ children: [
2437
+ /* @__PURE__ */ jsxs8("text", { children: [
2438
+ /* @__PURE__ */ jsx8("span", { fg: isActive ? "#66bb6a" : "#555555", children: isActive ? " \u25B8 " : " \u2514 " }),
2439
+ isActive ? /* @__PURE__ */ jsx8("span", { fg: nameColor, children: /* @__PURE__ */ jsx8("strong", { children: item.repoName }) }) : /* @__PURE__ */ jsx8("span", { fg: nameColor, children: item.repoName })
2440
+ ] }),
2441
+ /* @__PURE__ */ jsxs8("text", { children: [
2442
+ item.added > 0 && /* @__PURE__ */ jsxs8("span", { fg: "#66bb6a", children: [
2443
+ "+",
2444
+ item.added
2445
+ ] }),
2446
+ item.added > 0 && item.removed > 0 && /* @__PURE__ */ jsx8("span", { fg: "#444444", children: " " }),
2447
+ item.removed > 0 && /* @__PURE__ */ jsxs8("span", { fg: "#ff4444", children: [
2448
+ "-",
2449
+ item.removed
2450
+ ] })
2451
+ ] })
2452
+ ]
2453
+ },
2454
+ `diff-${i}`
2455
+ );
2456
+ })
2457
+ ] });
2458
+ })() }),
2459
+ env && /* @__PURE__ */ jsxs8(Section, { title: "Integrations", children: [
2460
+ /* @__PURE__ */ jsx8(CardItem, { label: "GitHub", status: env.githubAccessConfigured }),
2461
+ /* @__PURE__ */ jsx8(CardItem, { label: "Slack", status: env.slackAccessConfigured }),
2462
+ /* @__PURE__ */ jsx8(CardItem, { label: "Linear", status: env.linearAccessConfigured })
1809
2463
  ] }),
1810
- previews.length > 0 && /* @__PURE__ */ jsx7(Section, { title: "Previews", children: previews.map((preview, i) => {
2464
+ previews.length > 0 && /* @__PURE__ */ jsx8(Section, { title: "Previews", children: previews.map((preview, i) => {
1811
2465
  const previewItem = findItem("preview", String(preview.port));
1812
- return /* @__PURE__ */ jsxs7(
2466
+ return /* @__PURE__ */ jsxs8(
1813
2467
  "box",
1814
2468
  {
2469
+ id: previewItem ? getItemId(previewItem) : void 0,
1815
2470
  flexDirection: "row",
1816
2471
  justifyContent: "space-between",
1817
2472
  paddingX: 1,
1818
2473
  backgroundColor: previewItem && isHighlighted(previewItem) ? "#1a2a1a" : "#111111",
1819
2474
  onMouseDown: () => previewItem && handleAction(previewItem),
1820
2475
  children: [
1821
- /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsxs7("span", { fg: "#cccccc", children: [
2476
+ /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsxs8("span", { fg: "#cccccc", children: [
1822
2477
  ":",
1823
2478
  preview.port
1824
2479
  ] }) }),
1825
- /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: previewItem && isHighlighted(previewItem) ? "#66bb6a" : "#7dcfff", children: "\u2197" }) })
2480
+ /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: previewItem && isHighlighted(previewItem) ? "#66bb6a" : "#7dcfff", children: "\u2197" }) })
1826
2481
  ]
1827
2482
  },
1828
2483
  i
1829
2484
  );
1830
2485
  }) }),
1831
- status.repoStatuses && status.repoStatuses.length > 0 && /* @__PURE__ */ jsx7(Section, { title: "Repositories", children: status.repoStatuses.map((repo, i) => {
2486
+ status.repoStatuses && status.repoStatuses.length > 0 && /* @__PURE__ */ jsx8(Section, { title: "Repositories", children: status.repoStatuses.map((repo, i) => {
1832
2487
  const prItem = findItem("pr", repo.name);
1833
- return /* @__PURE__ */ jsxs7(React5.Fragment, { children: [
1834
- /* @__PURE__ */ jsx7("box", { backgroundColor: "#111111", paddingX: 1, children: /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx7("strong", { children: repo.name }) }) }) }),
1835
- /* @__PURE__ */ jsx7("box", { backgroundColor: "#111111", paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: /* @__PURE__ */ jsxs7("text", { children: [
1836
- /* @__PURE__ */ jsxs7("span", { fg: "#555555", children: [
2488
+ return /* @__PURE__ */ jsxs8(React6.Fragment, { children: [
2489
+ /* @__PURE__ */ jsx8("box", { backgroundColor: "#111111", paddingX: 1, children: /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx8("strong", { children: repo.name }) }) }) }),
2490
+ /* @__PURE__ */ jsx8("box", { backgroundColor: "#111111", paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: /* @__PURE__ */ jsxs8("text", { children: [
2491
+ /* @__PURE__ */ jsxs8("span", { fg: "#555555", children: [
1837
2492
  "\u2514",
1838
2493
  " "
1839
2494
  ] }),
1840
- /* @__PURE__ */ jsx7("span", { fg: repo.currentBranch !== repo.defaultBranch ? "#ffaa00" : "#66bb6a", children: repo.currentBranch })
2495
+ /* @__PURE__ */ jsx8("span", { fg: repo.currentBranch !== repo.defaultBranch ? "#ffaa00" : "#66bb6a", children: repo.currentBranch })
1841
2496
  ] }) }),
1842
- repo.gitDiff && (repo.gitDiff.added > 0 || repo.gitDiff.removed > 0) && /* @__PURE__ */ jsx7("box", { backgroundColor: "#111111", paddingX: 1, flexDirection: "row", children: /* @__PURE__ */ jsxs7("text", { children: [
1843
- /* @__PURE__ */ jsx7("span", { fg: "#555555", children: " " }),
1844
- /* @__PURE__ */ jsxs7("span", { fg: "#66bb6a", children: [
1845
- "+",
1846
- repo.gitDiff.added
1847
- ] }),
1848
- /* @__PURE__ */ jsx7("span", { fg: "#444444", children: " / " }),
1849
- /* @__PURE__ */ jsxs7("span", { fg: "#ff4444", children: [
1850
- "-",
1851
- repo.gitDiff.removed
1852
- ] })
1853
- ] }) }),
1854
- prItem && /* @__PURE__ */ jsx7(
2497
+ prItem && /* @__PURE__ */ jsx8(
1855
2498
  InteractiveRow,
1856
2499
  {
2500
+ id: getItemId(prItem),
1857
2501
  label: ` ${getItemLabel(prItem)}`,
1858
2502
  highlighted: isHighlighted(prItem),
1859
2503
  onClick: () => handleAction(prItem)
@@ -1861,35 +2505,35 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1861
2505
  )
1862
2506
  ] }, i);
1863
2507
  }) }),
1864
- env && /* @__PURE__ */ jsxs7(Section, { title: "Hooks", children: [
1865
- /* @__PURE__ */ jsx7(CardItem, { label: "Global warm", status: env.globalWarmHookCompleted.status }),
1866
- env.repositories.map((repo, i) => /* @__PURE__ */ jsxs7(React5.Fragment, { children: [
1867
- /* @__PURE__ */ jsx7("box", { backgroundColor: "#111111", paddingX: 1, children: /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx7("strong", { children: repo.repositoryName }) }) }) }),
1868
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
1869
- /* @__PURE__ */ jsxs7("text", { children: [
1870
- /* @__PURE__ */ jsxs7("span", { fg: "#555555", children: [
2508
+ env && /* @__PURE__ */ jsxs8(Section, { title: "Hooks", children: [
2509
+ /* @__PURE__ */ jsx8(CardItem, { label: "Global warm", status: env.globalWarmHookCompleted.status }),
2510
+ env.repositories.map((repo, i) => /* @__PURE__ */ jsxs8(React6.Fragment, { children: [
2511
+ /* @__PURE__ */ jsx8("box", { backgroundColor: "#111111", paddingX: 1, children: /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx8("strong", { children: repo.repositoryName }) }) }) }),
2512
+ /* @__PURE__ */ jsxs8("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
2513
+ /* @__PURE__ */ jsxs8("text", { children: [
2514
+ /* @__PURE__ */ jsxs8("span", { fg: "#555555", children: [
1871
2515
  "\u251C",
1872
2516
  " "
1873
2517
  ] }),
1874
- /* @__PURE__ */ jsx7("span", { fg: "#cccccc", children: "warm" })
2518
+ /* @__PURE__ */ jsx8("span", { fg: "#cccccc", children: "warm" })
1875
2519
  ] }),
1876
- /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7(StatusDot, { status: repo.warmHookCompleted }) })
2520
+ /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8(StatusDot, { status: repo.warmHookCompleted }) })
1877
2521
  ] }),
1878
- /* @__PURE__ */ jsxs7("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
1879
- /* @__PURE__ */ jsxs7("text", { children: [
1880
- /* @__PURE__ */ jsxs7("span", { fg: "#555555", children: [
2522
+ /* @__PURE__ */ jsxs8("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
2523
+ /* @__PURE__ */ jsxs8("text", { children: [
2524
+ /* @__PURE__ */ jsxs8("span", { fg: "#555555", children: [
1881
2525
  "\u2514",
1882
2526
  " "
1883
2527
  ] }),
1884
- /* @__PURE__ */ jsx7("span", { fg: "#cccccc", children: "start" })
2528
+ /* @__PURE__ */ jsx8("span", { fg: "#cccccc", children: "start" })
1885
2529
  ] }),
1886
- /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7(StatusDot, { status: repo.startHookCompleted }) })
2530
+ /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8(StatusDot, { status: repo.startHookCompleted }) })
1887
2531
  ] })
1888
2532
  ] }, i))
1889
2533
  ] }),
1890
- env && (expectedSkills.length > 0 || env.skillsInstalled.length > 0) && /* @__PURE__ */ jsx7(Section, { title: "Skills", children: /* @__PURE__ */ jsx7(DetailList, { expected: expectedSkills, actual: env.skillsInstalled }) }),
1891
- env && (expectedGlobalVars.length > 0 || env.envVarsSet.length > 0) && /* @__PURE__ */ jsx7(Section, { title: "Env Vars", children: /* @__PURE__ */ jsx7(DetailList, { expected: expectedGlobalVars, actual: env.envVarsSet }) }),
1892
- env && (expectedGlobalFiles.length > 0 || env.filesUploaded.length > 0) && /* @__PURE__ */ jsx7(Section, { title: "Files", children: /* @__PURE__ */ jsx7(DetailList, { expected: expectedGlobalFiles, actual: env.filesUploaded }) })
2534
+ env && (expectedSkills.length > 0 || env.skillsInstalled.length > 0) && /* @__PURE__ */ jsx8(Section, { title: "Skills", children: /* @__PURE__ */ jsx8(DetailList, { expected: expectedSkills, actual: env.skillsInstalled }) }),
2535
+ env && (expectedGlobalVars.length > 0 || env.envVarsSet.length > 0) && /* @__PURE__ */ jsx8(Section, { title: "Env Vars", children: /* @__PURE__ */ jsx8(DetailList, { expected: expectedGlobalVars, actual: env.envVarsSet }) }),
2536
+ env && (expectedGlobalFiles.length > 0 || env.filesUploaded.length > 0) && /* @__PURE__ */ jsx8(Section, { title: "Files", children: /* @__PURE__ */ jsx8(DetailList, { expected: expectedGlobalFiles, actual: env.filesUploaded }) })
1893
2537
  ] })
1894
2538
  }
1895
2539
  );
@@ -1899,8 +2543,8 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, e
1899
2543
  import { useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
1900
2544
 
1901
2545
  // src/interactive/toast-context.tsx
1902
- import { createContext as createContext2, useContext as useContext2, useState as useState6, useCallback as useCallback5, useRef as useRef2 } from "react";
1903
- import { jsx as jsx8 } from "@opentui/react/jsx-runtime";
2546
+ import { createContext as createContext2, useContext as useContext2, useState as useState7, useCallback as useCallback6, useRef as useRef4 } from "react";
2547
+ import { jsx as jsx9 } from "@opentui/react/jsx-runtime";
1904
2548
  var ToastContext = createContext2(null);
1905
2549
  function useToast() {
1906
2550
  const ctx = useContext2(ToastContext);
@@ -1908,9 +2552,9 @@ function useToast() {
1908
2552
  return ctx;
1909
2553
  }
1910
2554
  function ToastProvider({ children }) {
1911
- const [current, setCurrent] = useState6(null);
1912
- const timeoutRef = useRef2(null);
1913
- const show = useCallback5((options) => {
2555
+ const [current, setCurrent] = useState7(null);
2556
+ const timeoutRef = useRef4(null);
2557
+ const show = useCallback6((options) => {
1914
2558
  const { message, variant = "info", duration = 3e3 } = options;
1915
2559
  setCurrent({ message, variant });
1916
2560
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
@@ -1918,15 +2562,15 @@ function ToastProvider({ children }) {
1918
2562
  setCurrent(null);
1919
2563
  }, duration);
1920
2564
  }, []);
1921
- const error = useCallback5((err) => {
2565
+ const error = useCallback6((err) => {
1922
2566
  const message = err instanceof Error ? err.message : "An error occurred";
1923
2567
  show({ message, variant: "error", duration: 5e3 });
1924
2568
  }, [show]);
1925
- return /* @__PURE__ */ jsx8(ToastContext.Provider, { value: { current, show, error }, children });
2569
+ return /* @__PURE__ */ jsx9(ToastContext.Provider, { value: { current, show, error }, children });
1926
2570
  }
1927
2571
 
1928
2572
  // src/interactive/components/Toast.tsx
1929
- import { jsx as jsx9 } from "@opentui/react/jsx-runtime";
2573
+ import { jsx as jsx10 } from "@opentui/react/jsx-runtime";
1930
2574
  var VARIANT_COLORS = {
1931
2575
  info: { border: "#66bb6a", bg: "#0a1a0a" },
1932
2576
  success: { border: "#66bb6a", bg: "#0a1a0a" },
@@ -1939,7 +2583,7 @@ function Toast() {
1939
2583
  if (!current) return null;
1940
2584
  const colors = VARIANT_COLORS[current.variant] ?? VARIANT_COLORS.info;
1941
2585
  const toastWidth = Math.min(60, width - 4);
1942
- return /* @__PURE__ */ jsx9(
2586
+ return /* @__PURE__ */ jsx10(
1943
2587
  "box",
1944
2588
  {
1945
2589
  position: "absolute",
@@ -1953,19 +2597,20 @@ function Toast() {
1953
2597
  border: true,
1954
2598
  borderStyle: "rounded",
1955
2599
  zIndex: 100,
1956
- children: /* @__PURE__ */ jsx9("text", { fg: "#ffffff", selectable: true, children: current.message })
2600
+ children: /* @__PURE__ */ jsx10("text", { fg: "#ffffff", selectable: true, children: current.message })
1957
2601
  }
1958
2602
  );
1959
2603
  }
1960
2604
 
1961
2605
  // src/interactive/App.tsx
1962
- import { jsx as jsx10, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
2606
+ import { jsx as jsx11, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
1963
2607
  var FOCUS_ORDER = ["sidebar", "chat-tabs", "chat-history", "chat-input", "info"];
1964
2608
  var MOCK_CHATS = [
1965
2609
  { id: "mock-claude", provider: "claude", title: "Claude Code", createdAt: "", updatedAt: "", processing: false },
1966
2610
  { id: "mock-codex", provider: "codex", title: "Codex", createdAt: "", updatedAt: "", processing: false }
1967
2611
  ];
1968
2612
  var MONOLITH_URL = process.env.REPLICAS_MONOLITH_URL || "https://api.replicas.dev";
2613
+ var workspacePollingStarted = false;
1969
2614
  var queryClient = new QueryClient({
1970
2615
  defaultOptions: {
1971
2616
  queries: {
@@ -1981,15 +2626,24 @@ var authValue = {
1981
2626
  queryClient
1982
2627
  };
1983
2628
  function App() {
1984
- return /* @__PURE__ */ jsx10(ReplicasAuthProvider, { value: authValue, children: /* @__PURE__ */ jsxs8(ToastProvider, { children: [
1985
- /* @__PURE__ */ jsx10(AppInner, {}),
1986
- /* @__PURE__ */ jsx10(Toast, {})
2629
+ return /* @__PURE__ */ jsx11(ReplicasAuthProvider, { value: authValue, children: /* @__PURE__ */ jsxs9(ToastProvider, { children: [
2630
+ /* @__PURE__ */ jsx11(AppInner, {}),
2631
+ /* @__PURE__ */ jsx11(Toast, {})
1987
2632
  ] }) });
1988
2633
  }
1989
2634
  function AppInner() {
1990
2635
  const renderer = useRenderer();
1991
2636
  const { width } = useTerminalDimensions3();
1992
2637
  const toast = useToast();
2638
+ const rendererRef = useRef5(renderer);
2639
+ rendererRef.current = renderer;
2640
+ if (!workspacePollingStarted) {
2641
+ workspacePollingStarted = true;
2642
+ setInterval(() => {
2643
+ queryClient.invalidateQueries({ queryKey: ["workspaces"] });
2644
+ rendererRef.current.requestRender();
2645
+ }, 3e4);
2646
+ }
1993
2647
  useEffect3(() => {
1994
2648
  const handler = (selection) => {
1995
2649
  const text = selection.getSelectedText();
@@ -2003,17 +2657,29 @@ function AppInner() {
2003
2657
  renderer.off("selection", handler);
2004
2658
  };
2005
2659
  }, [renderer, toast]);
2006
- const [selectedWorkspaceId, setSelectedWorkspaceId] = useState7(null);
2007
- const [selectedChatId, setSelectedChatId] = useState7(null);
2008
- const [focusPanel, setFocusPanel] = useState7("sidebar");
2009
- const [taskMode, setTaskMode] = useState7("build");
2010
- const [mockWorkspaces, setMockWorkspaces] = useState7([]);
2011
- const mockToRealRef = useRef3(/* @__PURE__ */ new Map());
2012
- const mockGroupRef = useRef3(/* @__PURE__ */ new Map());
2013
- const [mockMessages, setMockMessages] = useState7([]);
2014
- const [mockThinking, setMockThinking] = useState7(false);
2015
- const pendingMessageRef = useRef3(/* @__PURE__ */ new Map());
2016
- const mockIds = useMemo5(() => new Set(mockWorkspaces.map((m) => m.id)), [mockWorkspaces]);
2660
+ const [selectedWorkspaceId, setSelectedWorkspaceId] = useState8(null);
2661
+ const [selectedChatId, setSelectedChatId] = useState8(null);
2662
+ const [focusPanel, setFocusPanel] = useState8("sidebar");
2663
+ const [taskMode, setTaskMode] = useState8("build");
2664
+ const [mockWorkspaces, setMockWorkspaces] = useState8([]);
2665
+ const mockToRealRef = useRef5(/* @__PURE__ */ new Map());
2666
+ const mockGroupRef = useRef5(/* @__PURE__ */ new Map());
2667
+ const mockPollingRef = useRef5(null);
2668
+ if (mockWorkspaces.length > 0 && !mockPollingRef.current) {
2669
+ mockPollingRef.current = setInterval(() => {
2670
+ queryClient.invalidateQueries({ queryKey: ["workspaces"] });
2671
+ rendererRef.current.requestRender();
2672
+ }, 5e3);
2673
+ } else if (mockWorkspaces.length === 0 && mockPollingRef.current) {
2674
+ clearInterval(mockPollingRef.current);
2675
+ mockPollingRef.current = null;
2676
+ }
2677
+ const [mockMessages, setMockMessages] = useState8([]);
2678
+ const [mockThinking, setMockThinking] = useState8(false);
2679
+ const pendingMessageRef = useRef5(/* @__PURE__ */ new Map());
2680
+ const [viewingDiff, setViewingDiff] = useState8(null);
2681
+ const [lastViewedDiff, setLastViewedDiff] = useState8(null);
2682
+ const mockIds = useMemo6(() => new Set(mockWorkspaces.map((m) => m.id)), [mockWorkspaces]);
2017
2683
  const isMockSelected = selectedWorkspaceId ? mockIds.has(selectedWorkspaceId) : false;
2018
2684
  const { data: workspacesData, isLoading: loadingWorkspaces } = useWorkspaces(1, 100, "organization");
2019
2685
  const { data: reposData } = useRepositories();
@@ -2027,7 +2693,7 @@ function AppInner() {
2027
2693
  );
2028
2694
  const { data: chatsData } = useWorkspaceChats(isMockSelected ? null : selectedWorkspaceId);
2029
2695
  const chats = isMockSelected ? MOCK_CHATS : chatsData?.chats ?? [];
2030
- const resolvedChatId = useMemo5(() => {
2696
+ const resolvedChatId = useMemo6(() => {
2031
2697
  if (selectedChatId && chats.find((c) => c.id === selectedChatId)) {
2032
2698
  return selectedChatId;
2033
2699
  }
@@ -2045,22 +2711,22 @@ function AppInner() {
2045
2711
  const deleteWorkspaceMutation = useDeleteWorkspace();
2046
2712
  const wakeWorkspaceMutation = useWakeWorkspace();
2047
2713
  const generateNameMutation = useGenerateWorkspaceName();
2048
- const [wakingWorkspaceId, setWakingWorkspaceId] = useState7(null);
2714
+ const [wakingWorkspaceId, setWakingWorkspaceId] = useState8(null);
2049
2715
  const sendMessageMutation = useSendChatMessage(selectedWorkspaceId, resolvedChatId);
2050
2716
  const interruptMutation = useInterruptChat(selectedWorkspaceId, resolvedChatId);
2051
2717
  const workspaces = workspacesData?.workspaces ?? [];
2052
2718
  const repositories = reposData?.repositories ?? [];
2053
2719
  const repositorySets = setsData?.repository_sets ?? [];
2054
2720
  const previews = previewsData?.previews ?? [];
2055
- const allWorkspaces = useMemo5(() => [...mockWorkspaces, ...workspaces], [mockWorkspaces, workspaces]);
2056
- const groups = useMemo5(
2721
+ const allWorkspaces = useMemo6(() => [...mockWorkspaces, ...workspaces], [mockWorkspaces, workspaces]);
2722
+ const groups = useMemo6(
2057
2723
  () => buildGroups(allWorkspaces, workspacesData, repositories, repositorySets, mockGroupRef.current),
2058
2724
  [allWorkspaces, workspacesData, repositories, repositorySets]
2059
2725
  );
2060
2726
  const selectedWorkspace = allWorkspaces.find((ws) => ws.id === selectedWorkspaceId) ?? null;
2061
2727
  const selectedChat = chats.find((c) => c.id === resolvedChatId) ?? null;
2062
2728
  const isProcessing = mockThinking || (selectedChat?.processing ?? false);
2063
- const displayMessages = useMemo5(() => {
2729
+ const displayMessages = useMemo6(() => {
2064
2730
  if (isMockSelected) return mockMessages;
2065
2731
  const rawEvents = historyData?.events ?? [];
2066
2732
  const events = rawEvents.filter(isAgentBackendEvent);
@@ -2117,30 +2783,30 @@ function AppInner() {
2117
2783
  }
2118
2784
  sendMessageMutation.mutate({ message: pending });
2119
2785
  }, [selectedWorkspaceId, resolvedChatId, isMockSelected, statusData?.status]);
2120
- useEffect3(() => {
2121
- if (mockWorkspaces.length === 0) return;
2122
- const interval = setInterval(() => {
2123
- queryClient.invalidateQueries({ queryKey: ["workspaces"] });
2124
- }, 5e3);
2125
- return () => clearInterval(interval);
2126
- }, [mockWorkspaces.length]);
2127
- const handleSelectWorkspace = useCallback6((workspaceId) => {
2786
+ const handleSelectWorkspace = useCallback7((workspaceId) => {
2128
2787
  setSelectedWorkspaceId(workspaceId);
2129
2788
  setSelectedChatId(null);
2130
2789
  setMockMessages([]);
2131
2790
  setMockThinking(false);
2791
+ setViewingDiff(null);
2792
+ setLastViewedDiff(null);
2132
2793
  setFocusPanel("chat-input");
2133
2794
  }, []);
2134
- const handleFocus = useCallback6((panel) => {
2795
+ const handleFocus = useCallback7((panel) => {
2796
+ if (panel === "sidebar") {
2797
+ queryClient.invalidateQueries({ queryKey: ["workspaces"] });
2798
+ }
2135
2799
  setFocusPanel(panel);
2136
2800
  }, []);
2137
- const handleCreateWorkspace = useCallback6(
2801
+ const handleCreateWorkspace = useCallback7(
2138
2802
  async (groupId, groupType) => {
2139
2803
  const orgId = getOrganizationId() ?? "";
2140
2804
  const mock = createMockWorkspaceRecord(orgId);
2141
2805
  mockGroupRef.current.set(mock.id, groupId);
2142
2806
  setMockWorkspaces((prev) => [mock, ...prev]);
2143
2807
  setSelectedWorkspaceId(mock.id);
2808
+ setViewingDiff(null);
2809
+ setLastViewedDiff(null);
2144
2810
  setFocusPanel("chat-input");
2145
2811
  try {
2146
2812
  const request = groupType === "set" ? { repository_set_id: groupId.replace(/^set:/, ""), name: mock.name, placeholder: true } : { repository_ids: [groupId], name: mock.name, placeholder: true };
@@ -2154,18 +2820,26 @@ function AppInner() {
2154
2820
  },
2155
2821
  [createWorkspaceMutation, toast]
2156
2822
  );
2157
- const handleDeleteWorkspace = useCallback6(
2823
+ const handleDeleteWorkspace = useCallback7(
2158
2824
  async (workspaceId) => {
2159
2825
  if (mockIds.has(workspaceId)) {
2160
2826
  mockGroupRef.current.delete(workspaceId);
2161
2827
  mockToRealRef.current.delete(workspaceId);
2162
2828
  setMockWorkspaces((prev) => prev.filter((m) => m.id !== workspaceId));
2163
- if (selectedWorkspaceId === workspaceId) setSelectedWorkspaceId(null);
2829
+ if (selectedWorkspaceId === workspaceId) {
2830
+ setSelectedWorkspaceId(null);
2831
+ setViewingDiff(null);
2832
+ setLastViewedDiff(null);
2833
+ }
2164
2834
  return;
2165
2835
  }
2166
2836
  try {
2167
2837
  await deleteWorkspaceMutation.mutateAsync(workspaceId);
2168
- if (selectedWorkspaceId === workspaceId) setSelectedWorkspaceId(null);
2838
+ if (selectedWorkspaceId === workspaceId) {
2839
+ setSelectedWorkspaceId(null);
2840
+ setViewingDiff(null);
2841
+ setLastViewedDiff(null);
2842
+ }
2169
2843
  toast.show({ message: "Workspace deleted", variant: "info" });
2170
2844
  } catch (err) {
2171
2845
  toast.error(err);
@@ -2173,7 +2847,7 @@ function AppInner() {
2173
2847
  },
2174
2848
  [mockIds, selectedWorkspaceId, deleteWorkspaceMutation]
2175
2849
  );
2176
- const handleWakeWorkspace = useCallback6(
2850
+ const handleWakeWorkspace = useCallback7(
2177
2851
  async (workspaceId) => {
2178
2852
  setWakingWorkspaceId(workspaceId);
2179
2853
  try {
@@ -2187,10 +2861,10 @@ function AppInner() {
2187
2861
  },
2188
2862
  [wakeWorkspaceMutation, toast]
2189
2863
  );
2190
- const handleSelectChat = useCallback6((chatId) => {
2864
+ const handleSelectChat = useCallback7((chatId) => {
2191
2865
  setSelectedChatId(chatId);
2192
2866
  }, []);
2193
- useKeyboard4((key) => {
2867
+ useKeyboard5((key) => {
2194
2868
  if (key.name === "f12") {
2195
2869
  renderer.console.toggle();
2196
2870
  return;
@@ -2210,12 +2884,17 @@ function AppInner() {
2210
2884
  const availablePanels = FOCUS_ORDER.filter((p) => {
2211
2885
  if (p === "sidebar" && !showWorkspacePanel) return false;
2212
2886
  if ((p === "chat-tabs" || p === "chat-history" || p === "chat-input") && (!selectedWorkspaceId || statusData?.status === "sleeping")) return false;
2887
+ if (viewingDiff && (p === "chat-tabs" || p === "chat-input")) return false;
2213
2888
  if (p === "chat-tabs" && chats.length <= 1) return false;
2214
2889
  if (p === "info" && !showInfoPanel) return false;
2215
2890
  return true;
2216
2891
  });
2217
2892
  const idx = availablePanels.indexOf(current);
2218
- return availablePanels[(idx + 1) % availablePanels.length];
2893
+ const next = availablePanels[(idx + 1) % availablePanels.length];
2894
+ if (next === "sidebar") {
2895
+ queryClient.invalidateQueries({ queryKey: ["workspaces"] });
2896
+ }
2897
+ return next;
2219
2898
  });
2220
2899
  return;
2221
2900
  }
@@ -2223,6 +2902,15 @@ function AppInner() {
2223
2902
  setTaskMode((m) => m === "build" ? "plan" : "build");
2224
2903
  return;
2225
2904
  }
2905
+ if ((key.name === "1" || key.name === "2") && focusPanel !== "chat-input") {
2906
+ if (key.name === "1") {
2907
+ setViewingDiff(null);
2908
+ } else if (firstAvailableDiff) {
2909
+ setViewingDiff(firstAvailableDiff);
2910
+ setLastViewedDiff(firstAvailableDiff);
2911
+ }
2912
+ return;
2913
+ }
2226
2914
  if (key.name === "w" && focusPanel !== "sidebar" && focusPanel !== "info" && selectedWorkspaceId) {
2227
2915
  if (statusData?.status === "sleeping" && !wakingWorkspaceId) {
2228
2916
  handleWakeWorkspace(selectedWorkspaceId);
@@ -2231,7 +2919,33 @@ function AppInner() {
2231
2919
  }
2232
2920
  if (focusPanel === "chat-input") return;
2233
2921
  });
2234
- const handleSendMessage = useCallback6(
2922
+ const handleViewDiff = useCallback7((diff, repoName) => {
2923
+ setViewingDiff({ diff, repoName });
2924
+ setLastViewedDiff({ diff, repoName });
2925
+ setFocusPanel("chat-history");
2926
+ }, []);
2927
+ const handleSelectChatMode = useCallback7(() => {
2928
+ setViewingDiff(null);
2929
+ setFocusPanel("chat-history");
2930
+ }, []);
2931
+ const firstAvailableDiff = useMemo6(() => {
2932
+ if (lastViewedDiff) return lastViewedDiff;
2933
+ const repos = statusData?.repoStatuses ?? [];
2934
+ for (const repo of repos) {
2935
+ if (repo.gitDiff?.fullDiff && (repo.gitDiff.added > 0 || repo.gitDiff.removed > 0)) {
2936
+ return { diff: repo.gitDiff.fullDiff, repoName: repo.name };
2937
+ }
2938
+ }
2939
+ return null;
2940
+ }, [lastViewedDiff, statusData?.repoStatuses]);
2941
+ const handleSelectDiffMode = useCallback7(() => {
2942
+ if (firstAvailableDiff) {
2943
+ setViewingDiff(firstAvailableDiff);
2944
+ setLastViewedDiff(firstAvailableDiff);
2945
+ setFocusPanel("chat-history");
2946
+ }
2947
+ }, [firstAvailableDiff]);
2948
+ const handleSendMessage = useCallback7(
2235
2949
  async (message) => {
2236
2950
  if (!selectedWorkspaceId || !resolvedChatId) return;
2237
2951
  if (isMockSelected) {
@@ -2248,9 +2962,9 @@ function AppInner() {
2248
2962
  },
2249
2963
  [selectedWorkspaceId, resolvedChatId, isMockSelected, sendMessageMutation, taskMode]
2250
2964
  );
2251
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", width: "100%", height: "100%", backgroundColor: "#000000", children: [
2252
- /* @__PURE__ */ jsxs8("box", { flexDirection: "row", flexGrow: 1, backgroundColor: "#000000", children: [
2253
- showWorkspacePanel && /* @__PURE__ */ jsx10(
2965
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", height: "100%", backgroundColor: "#000000", children: [
2966
+ /* @__PURE__ */ jsxs9("box", { flexDirection: "row", flexGrow: 1, backgroundColor: "#000000", children: [
2967
+ showWorkspacePanel && /* @__PURE__ */ jsx11(
2254
2968
  WorkspaceSidebar,
2255
2969
  {
2256
2970
  groups,
@@ -2265,7 +2979,20 @@ function AppInner() {
2265
2979
  loading: loadingWorkspaces
2266
2980
  }
2267
2981
  ),
2268
- selectedWorkspaceId && statusData?.status !== "sleeping" ? /* @__PURE__ */ jsx10(
2982
+ /* @__PURE__ */ jsx11("box", { flexDirection: "column", flexGrow: 1, children: viewingDiff ? /* @__PURE__ */ jsx11(
2983
+ "box",
2984
+ {
2985
+ flexGrow: 1,
2986
+ border: true,
2987
+ borderStyle: "rounded",
2988
+ borderColor: focusPanel !== "sidebar" && focusPanel !== "info" ? "#66bb6a" : "#333333",
2989
+ backgroundColor: "#000000",
2990
+ flexDirection: "column",
2991
+ title: "Diff",
2992
+ titleAlignment: "center",
2993
+ children: /* @__PURE__ */ jsx11(DiffViewer, { diff: viewingDiff.diff, repoName: viewingDiff.repoName, focused: focusPanel !== "sidebar" && focusPanel !== "info" })
2994
+ }
2995
+ ) : selectedWorkspaceId && statusData?.status !== "sleeping" ? /* @__PURE__ */ jsx11(
2269
2996
  ChatArea,
2270
2997
  {
2271
2998
  chats,
@@ -2279,7 +3006,7 @@ function AppInner() {
2279
3006
  isProcessing,
2280
3007
  loading: loadingMessages
2281
3008
  }
2282
- ) : selectedWorkspaceId && statusData?.status === "sleeping" ? /* @__PURE__ */ jsxs8(
3009
+ ) : selectedWorkspaceId && statusData?.status === "sleeping" ? /* @__PURE__ */ jsxs9(
2283
3010
  "box",
2284
3011
  {
2285
3012
  flexGrow: 1,
@@ -2292,18 +3019,18 @@ function AppInner() {
2292
3019
  flexDirection: "column",
2293
3020
  gap: 1,
2294
3021
  children: [
2295
- /* @__PURE__ */ jsxs8("text", { fg: "#888888", children: [
3022
+ /* @__PURE__ */ jsxs9("text", { fg: "#888888", children: [
2296
3023
  "\u263E",
2297
3024
  " This workspace is sleeping"
2298
3025
  ] }),
2299
- /* @__PURE__ */ jsxs8("text", { fg: "#555555", children: [
3026
+ /* @__PURE__ */ jsxs9("text", { fg: "#555555", children: [
2300
3027
  "Press ",
2301
- /* @__PURE__ */ jsx10("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx10("strong", { children: "w" }) }),
3028
+ /* @__PURE__ */ jsx11("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx11("strong", { children: "w" }) }),
2302
3029
  " to wake it up"
2303
3030
  ] })
2304
3031
  ]
2305
3032
  }
2306
- ) : /* @__PURE__ */ jsx10(
3033
+ ) : /* @__PURE__ */ jsx11(
2307
3034
  "box",
2308
3035
  {
2309
3036
  flexGrow: 1,
@@ -2313,10 +3040,10 @@ function AppInner() {
2313
3040
  backgroundColor: "#000000",
2314
3041
  justifyContent: "center",
2315
3042
  alignItems: "center",
2316
- children: /* @__PURE__ */ jsx10("text", { fg: "#555555", children: "Create a workspace to begin building!" })
3043
+ children: /* @__PURE__ */ jsx11("text", { fg: "#555555", children: "Create a workspace to begin building!" })
2317
3044
  }
2318
- ),
2319
- showInfoPanel && /* @__PURE__ */ jsx10(
3045
+ ) }),
3046
+ showInfoPanel && /* @__PURE__ */ jsx11(
2320
3047
  WorkspaceInfo,
2321
3048
  {
2322
3049
  status: statusData ?? null,
@@ -2331,31 +3058,38 @@ function AppInner() {
2331
3058
  },
2332
3059
  previews,
2333
3060
  onWakeWorkspace: handleWakeWorkspace,
3061
+ onViewDiff: handleViewDiff,
2334
3062
  onCreatePr: () => {
2335
3063
  if (!resolvedChatId) return;
2336
3064
  sendMessageMutation.mutate({ message: "Push changes and create a GitHub PR" });
2337
3065
  },
2338
3066
  isPlanMode: taskMode === "plan",
2339
- wakingWorkspaceId
3067
+ wakingWorkspaceId,
3068
+ viewMode: viewingDiff ? "diff" : "chat",
3069
+ viewingDiffRepoName: viewingDiff?.repoName ?? null,
3070
+ onSelectChatMode: handleSelectChatMode,
3071
+ onSelectDiffMode: handleSelectDiffMode
2340
3072
  }
2341
3073
  )
2342
3074
  ] }),
2343
- /* @__PURE__ */ jsx10(
3075
+ /* @__PURE__ */ jsx11(
2344
3076
  StatusBar,
2345
3077
  {
2346
- focusPanel
3078
+ focusPanel,
3079
+ viewingDiff: !!viewingDiff,
3080
+ hasDiffAvailable: !!firstAvailableDiff
2347
3081
  }
2348
3082
  )
2349
3083
  ] });
2350
3084
  }
2351
3085
 
2352
3086
  // src/interactive/index.tsx
2353
- import { jsx as jsx11 } from "@opentui/react/jsx-runtime";
3087
+ import { jsx as jsx12 } from "@opentui/react/jsx-runtime";
2354
3088
  async function launchInteractive() {
2355
3089
  const renderer = await createCliRenderer({
2356
3090
  exitOnCtrlC: false
2357
3091
  });
2358
- createRoot(renderer).render(/* @__PURE__ */ jsx11(App, {}));
3092
+ createRoot(renderer).render(/* @__PURE__ */ jsx12(App, {}));
2359
3093
  }
2360
3094
  export {
2361
3095
  launchInteractive