| ... | @@ -57,14 +57,12 @@ function editorLabel(editor: (typeof EDITORS)[number]): string { | ... | @@ -57,14 +57,12 @@ function editorLabel(editor: (typeof EDITORS)[number]): string { |
| 57 | | 57 | |
| 58 | const LAST_EDITOR_KEY = "t3code:last-editor"; | 58 | const LAST_EDITOR_KEY = "t3code:last-editor"; |
| 59 | const MAX_VISIBLE_WORK_LOG_ENTRIES = 6; | 59 | const MAX_VISIBLE_WORK_LOG_ENTRIES = 6; |
| 60 | const GIT_ACTION_ITEMS: Array<{ | 60 | |
| | 61 | interface GitActionMenuItem { |
| 61 | id: GitStackedAction; | 62 | id: GitStackedAction; |
| 62 | label: string; | 63 | label: string; |
| 63 | }> = [ | 64 | disabled: boolean; |
| 64 | { id: "commit", label: "Commit" }, | 65 | } |
| 65 | { id: "commit_push", label: "Commit & Push" }, | | |
| 66 | { id: "commit_push_pr", label: "Commit, Push & Create PR" }, | | |
| 67 | ]; | | |
| 68 | | 66 | |
| 69 | function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { | 67 | function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { |
| 70 | if (tone === "error") return "text-rose-300/50 dark:text-rose-300/50"; | 68 | if (tone === "error") return "text-rose-300/50 dark:text-rose-300/50"; |
| ... | @@ -299,9 +297,74 @@ export default function ChatView() { | ... | @@ -299,9 +297,74 @@ export default function ChatView() { |
| 299 | sandboxMode: "workspace-write", | 297 | sandboxMode: "workspace-write", |
| 300 | } as const); | 298 | } as const); |
| 301 | const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null; | 299 | const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null; |
| 302 | const gitActionsDisabled = | 300 | const gitBaseDisabled = |
| 303 | !api || !gitCwd || !gitStatus || isGitActionRunning; | 301 | !api || !gitCwd || !gitStatus || isGitActionRunning; |
| 304 | const pushActionDisabled = gitActionsDisabled || gitStatus.branch === null; | 302 | const gitActionMenuItems = useMemo<GitActionMenuItem[]>(() => { |
| | 303 | if (!gitStatus) return []; |
| | 304 | |
| | 305 | const hasBranch = gitStatus.branch !== null; |
| | 306 | const canCommit = !gitBaseDisabled && gitStatus.hasWorkingTreeChanges; |
| | 307 | const canPushOrPr = !gitBaseDisabled && hasBranch; |
| | 308 | |
| | 309 | if (!hasBranch) { |
| | 310 | return [ |
| | 311 | { |
| | 312 | id: "commit", |
| | 313 | label: "Commit", |
| | 314 | disabled: !canCommit, |
| | 315 | }, |
| | 316 | ]; |
| | 317 | } |
| | 318 | |
| | 319 | if (gitStatus.hasWorkingTreeChanges) { |
| | 320 | return [ |
| | 321 | { |
| | 322 | id: "commit", |
| | 323 | label: "Commit", |
| | 324 | disabled: !canCommit, |
| | 325 | }, |
| | 326 | { |
| | 327 | id: "commit_push", |
| | 328 | label: "Commit & Push", |
| | 329 | disabled: !canPushOrPr, |
| | 330 | }, |
| | 331 | { |
| | 332 | id: "commit_push_pr", |
| | 333 | label: "Commit, Push & Create PR", |
| | 334 | disabled: !canPushOrPr, |
| | 335 | }, |
| | 336 | ]; |
| | 337 | } |
| | 338 | |
| | 339 | if (gitStatus.aheadCount > 0) { |
| | 340 | return [ |
| | 341 | { |
| | 342 | id: "commit", |
| | 343 | label: "Commit", |
| | 344 | disabled: true, |
| | 345 | }, |
| | 346 | { |
| | 347 | id: "commit_push", |
| | 348 | label: "Commit & Push", |
| | 349 | disabled: !canPushOrPr, |
| | 350 | }, |
| | 351 | { |
| | 352 | id: "commit_push_pr", |
| | 353 | label: "Commit, Push & Create PR", |
| | 354 | disabled: !canPushOrPr, |
| | 355 | }, |
| | 356 | ]; |
| | 357 | } |
| | 358 | |
| | 359 | const openPrDisabled = !canPushOrPr || gitStatus.behindCount > 0; |
| | 360 | return [ |
| | 361 | { |
| | 362 | id: "commit_push_pr", |
| | 363 | label: "Open PR", |
| | 364 | disabled: openPrDisabled, |
| | 365 | }, |
| | 366 | ]; |
| | 367 | }, [gitBaseDisabled, gitStatus]); |
| 305 | | 368 | |
| 306 | useEffect(() => { | 369 | useEffect(() => { |
| 307 | latestGitCwdRef.current = gitCwd; | 370 | latestGitCwdRef.current = gitCwd; |
| ... | @@ -809,15 +872,13 @@ export default function ChatView() { | ... | @@ -809,15 +872,13 @@ export default function ChatView() { |
| 809 | <p className="px-2 pb-1 text-[10px] uppercase tracking-[0.1em] text-muted-foreground/60"> | 872 | <p className="px-2 pb-1 text-[10px] uppercase tracking-[0.1em] text-muted-foreground/60"> |
| 810 | Git actions | 873 | Git actions |
| 811 | </p> | 874 | </p> |
| 812 | {GIT_ACTION_ITEMS.map((item) => { | 875 | {gitActionMenuItems.map((item) => { |
| 813 | const disabled = | | |
| 814 | item.id === "commit" ? gitActionsDisabled : pushActionDisabled; | | |
| 815 | return ( | 876 | return ( |
| 816 | <button | 877 | <button |
| 817 | key={item.id} | 878 | key={item.id} |
| 818 | type="button" | 879 | type="button" |
| 819 | className="mb-1 flex w-full items-center rounded-lg px-2 py-2 text-left text-[11px] text-foreground transition-colors duration-150 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50" | 880 | className="mb-1 flex w-full items-center rounded-lg px-2 py-2 text-left text-[11px] text-foreground transition-colors duration-150 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50" |
| 820 | disabled={disabled} | 881 | disabled={item.disabled} |
| 821 | onClick={() => { | 882 | onClick={() => { |
| 822 | void runGitAction(item.id); | 883 | void runGitAction(item.id); |
| 823 | }} | 884 | }} |
| ... | @@ -831,6 +892,15 @@ export default function ChatView() { | ... | @@ -831,6 +892,15 @@ export default function ChatView() { |
| 831 | Detached HEAD: push and PR actions are unavailable. | 892 | Detached HEAD: push and PR actions are unavailable. |
| 832 | </p> | 893 | </p> |
| 833 | )} | 894 | )} |
| | 895 | {gitStatus && |
| | 896 | gitStatus.branch !== null && |
| | 897 | !gitStatus.hasWorkingTreeChanges && |
| | 898 | gitStatus.aheadCount === 0 && |
| | 899 | gitStatus.behindCount > 0 && ( |
| | 900 | <p className="px-2 pt-1 text-[10px] text-amber-500 dark:text-amber-300"> |
| | 901 | Branch is behind upstream. Pull/rebase before opening a PR. |
| | 902 | </p> |
| | 903 | )} |
| 834 | </div> | 904 | </div> |
| 835 | )} | 905 | )} |
| 836 | </div> | 906 | </div> |
| ... | @@ -858,13 +928,13 @@ export default function ChatView() { | ... | @@ -858,13 +928,13 @@ export default function ChatView() { |
| 858 | )} | 928 | )} |
| 859 | | 929 | |
| 860 | {gitActionError && ( | 930 | {gitActionError && ( |
| 861 | <div className="mx-4 mt-3 rounded-lg border border-rose-400/20 bg-rose-900/20 px-3 py-2 text-xs text-rose-200"> | 931 | <div className="mx-4 mt-3 rounded-lg border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-800 dark:border-rose-400/20 dark:bg-rose-900/20 dark:text-rose-200"> |
| 862 | {gitActionError} | 932 | {gitActionError} |
| 863 | </div> | 933 | </div> |
| 864 | )} | 934 | )} |
| 865 | | 935 | |
| 866 | {gitActionNotice && ( | 936 | {gitActionNotice && ( |
| 867 | <div className="mx-4 mt-3 rounded-lg border border-emerald-400/20 bg-emerald-500/[0.08] px-3 py-2 text-xs text-emerald-100"> | 937 | <div className="mx-4 mt-3 rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs text-emerald-800 dark:border-emerald-400/20 dark:bg-emerald-500/[0.08] dark:text-emerald-100"> |
| 868 | {gitActionNotice} | 938 | {gitActionNotice} |
| 869 | </div> | 939 | </div> |
| 870 | )} | 940 | )} |