authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-12 11:20:38-08:00
committergravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-12 11:20:38-08:00
log3c939e8e694bbba5b16bd13d617fb0d41f229eb0
tree308237a08f65d036a4a8ae7303a5aee108f8d8c9
parent6fc632f9a162abcfaa8ed1bdc659ecce526f26ed

Use Lucide icons for ChatView git action menu

- Replace custom inline SVGs with Lucide commit, push, and GitHub icons - Keep git action behavior unchanged while applying minor style cleanup

1 files changed, 15 insertions(+), 71 deletions(-)

apps/web/src/components/ChatView.tsx+15-71
......@@ -40,6 +40,7 @@ import BranchToolbar from "./BranchToolbar";
4040import { isTerminalToggleShortcut } from "../terminal-shortcuts";
4141import ChatMarkdown from "./ChatMarkdown";
4242import ThreadTerminalDrawer from "./ThreadTerminalDrawer";
43import { CloudUploadIcon, GitCommitIcon, GithubIcon } from "lucide-react";
4344
4445function formatMessageMeta(createdAt: string, duration: string | null): string {
4546 if (!duration) return formatTimestamp(createdAt);
......@@ -70,62 +71,14 @@ function GitActionIcon(props: { icon: GitActionMenuItem["icon"]; disabled: boole
7071 const toneClass = props.disabled ? "text-muted-foreground/45" : "text-foreground/85";
7172
7273 if (props.icon === "commit") {
73 return (
74 <svg
75 viewBox="0 0 20 20"
76 aria-hidden="true"
77 className={`h-5 w-5 shrink-0 ${toneClass}`}
78 fill="none"
79 stroke="currentColor"
80 strokeWidth="1.8"
81 strokeLinecap="round"
82 strokeLinejoin="round"
83 >
84 <circle cx="10" cy="10" r="4.2" />
85 <path d="M1.8 10h4.2" />
86 <path d="M14 10h4.2" />
87 </svg>
88 );
74 return <GitCommitIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />;
8975 }
9076
9177 if (props.icon === "push") {
92 return (
93 <svg
94 viewBox="0 0 20 20"
95 aria-hidden="true"
96 className={`h-5 w-5 shrink-0 ${toneClass}`}
97 fill="none"
98 stroke="currentColor"
99 strokeWidth="1.8"
100 strokeLinecap="round"
101 strokeLinejoin="round"
102 >
103 <path d="M5.2 16h9.6a3.2 3.2 0 0 0 .2-6.4h-.5A4.8 4.8 0 0 0 5 9 3.7 3.7 0 0 0 5.2 16Z" />
104 <path d="M10 5.8v6.2" />
105 <path d="m7.7 8.1 2.3-2.3 2.3 2.3" />
106 </svg>
107 );
78 return <CloudUploadIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />;
10879 }
10980
110 return (
111 <svg
112 viewBox="0 0 20 20"
113 aria-hidden="true"
114 className={`h-5 w-5 shrink-0 ${toneClass}`}
115 fill="none"
116 stroke="currentColor"
117 strokeWidth="1.8"
118 strokeLinecap="round"
119 strokeLinejoin="round"
120 >
121 <circle cx="6" cy="5" r="1.7" />
122 <circle cx="6" cy="15" r="1.7" />
123 <circle cx="14" cy="10" r="1.7" />
124 <path d="M6 6.8v6.5" />
125 <path d="M7.8 6.1c2.9 0 3.8 1.4 5.1 2.9" />
126 <path d="M7.8 13.9c2.9 0 3.8-1.4 5.1-2.9" />
127 </svg>
128 );
81 return <GithubIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />;
12982}
13083
13184function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string {
......@@ -334,8 +287,7 @@ export default function ChatView() {
334287 sandboxMode: "workspace-write",
335288 } as const);
336289 const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null;
337 const gitBaseDisabled =
338 !api || !gitCwd || !gitStatus || isGitActionRunning;
290 const gitBaseDisabled = !api || !gitCwd || !gitStatus || isGitActionRunning;
339291 const gitActionMenuItems = useMemo<GitActionMenuItem[]>(() => {
340292 if (!gitStatus) return [];
341293
......@@ -343,7 +295,9 @@ export default function ChatView() {
343295 const hasOpenPr = gitStatus.openPr !== null;
344296 const canCommit = !gitBaseDisabled && gitStatus.hasWorkingTreeChanges;
345297 const canPush =
346 !gitBaseDisabled && hasBranch && (gitStatus.hasWorkingTreeChanges || gitStatus.aheadCount > 0);
298 !gitBaseDisabled &&
299 hasBranch &&
300 (gitStatus.hasWorkingTreeChanges || gitStatus.aheadCount > 0);
347301 const canViewPr =
348302 !gitBaseDisabled &&
349303 hasBranch &&
......@@ -403,9 +357,7 @@ export default function ChatView() {
403357 action,
404358 });
405359 } catch (error) {
406 setGitActionError(
407 error instanceof Error ? error.message : "Git action failed.",
408 );
360 setGitActionError(error instanceof Error ? error.message : "Git action failed.");
409361 } finally {
410362 setIsGitActionRunning(false);
411363 try {
......@@ -425,9 +377,7 @@ export default function ChatView() {
425377 (activeThread.messages.length > 0 ||
426378 (activeThread.session !== null && activeThread.session.status !== "closed")),
427379 );
428 const terminalShortcutHint = navigator.platform.includes("Mac")
429 ? "\u2318J"
430 : "Ctrl+J";
380 const terminalShortcutHint = navigator.platform.includes("Mac") ? "\u2318J" : "Ctrl+J";
431381 const focusComposer = useCallback(() => {
432382 const textarea = textareaRef.current;
433383 if (!textarea) return;
......@@ -505,11 +455,7 @@ export default function ChatView() {
505455 } catch (error) {
506456 if (!cancelled) {
507457 setGitStatus(null);
508 setGitActionError(
509 error instanceof Error
510 ? error.message
511 : "Failed to read git status.",
512 );
458 setGitActionError(error instanceof Error ? error.message : "Failed to read git status.");
513459 }
514460 }
515461 };
......@@ -927,9 +873,7 @@ export default function ChatView() {
927873 </button>
928874 {isGitMenuOpen && (
929875 <div className="absolute right-0 top-full z-50 mt-1 w-[280px] rounded-3xl border border-border bg-popover p-3 shadow-xl">
930 <p className="px-3 pb-2 text-[13px] text-muted-foreground/75">
931 Git actions
932 </p>
876 <p className="px-3 pb-2 text-[13px] text-muted-foreground/75">Git actions</p>
933877 {gitActionMenuItems.map((item) => {
934878 return (
935879 <button
......@@ -1036,7 +980,7 @@ export default function ChatView() {
1036980 </button>
1037981 <button
1038982 type="button"
1039 className="rounded-md border border-sky-300/30 bg-sky-500/[0.15] px-2 py-1 text-[11px] text-sky-100 transition-colors duration-150 hover:bg-sky-500/[0.22] disabled:cursor-not-allowed disabled:opacity-50"
983 className="rounded-md border border-sky-300/30 bg-sky-500/15 px-2 py-1 text-[11px] text-sky-100 transition-colors duration-150 hover:bg-sky-500/22 disabled:cursor-not-allowed disabled:opacity-50"
1040984 disabled={isResponding}
1041985 onClick={() => void onRespondToApproval(approval.requestId, "acceptForSession")}
1042986 >
......@@ -1052,7 +996,7 @@ export default function ChatView() {
1052996 </button>
1053997 <button
1054998 type="button"
1055 className="rounded-md border border-rose-300/30 bg-rose-500/[0.12] px-2 py-1 text-[11px] text-rose-100 transition-colors duration-150 hover:bg-rose-500/[0.2] disabled:cursor-not-allowed disabled:opacity-50"
999 className="rounded-md border border-rose-300/30 bg-rose-500/12 px-2 py-1 text-[11px] text-rose-100 transition-colors duration-150 hover:bg-rose-500/20 disabled:cursor-not-allowed disabled:opacity-50"
10561000 disabled={isResponding}
10571001 onClick={() => void onRespondToApproval(approval.requestId, "cancel")}
10581002 >
......@@ -1171,7 +1115,7 @@ export default function ChatView() {
11711115 <Fragment key={timelineEntry.id}>
11721116 <div className="flex justify-end">
11731117 <div className="max-w-[80%] rounded-2xl rounded-br-sm border border-border bg-secondary px-4 py-3">
1174 <pre className="whitespace-pre-wrap break-words font-mono text-sm leading-relaxed text-foreground">
1118 <pre className="whitespace-pre-wrap wrap-break-word font-mono text-sm leading-relaxed text-foreground">
11751119 {timelineEntry.message.text}
11761120 </pre>
11771121 <p className="mt-1.5 text-right text-[10px] text-muted-foreground/30">