| ... | ... | @@ -57,14 +57,12 @@ function editorLabel(editor: (typeof EDITORS)[number]): string { |
| 57 | 57 | |
| 58 | 58 | const LAST_EDITOR_KEY = "t3code:last-editor"; |
| 59 | 59 | const MAX_VISIBLE_WORK_LOG_ENTRIES = 6; |
| 60 | | const GIT_ACTION_ITEMS: Array<{ |
| 60 | |
| 61 | interface GitActionMenuItem { |
| 61 | 62 | id: GitStackedAction; |
| 62 | 63 | label: string; |
| 63 | | }> = [ |
| 64 | | { id: "commit", label: "Commit" }, |
| 65 | | { id: "commit_push", label: "Commit & Push" }, |
| 66 | | { id: "commit_push_pr", label: "Commit, Push & Create PR" }, |
| 67 | | ]; |
| 64 | disabled: boolean; |
| 65 | } |
| 68 | 66 | |
| 69 | 67 | function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { |
| 70 | 68 | if (tone === "error") return "text-rose-300/50 dark:text-rose-300/50"; |
| ... | ... | @@ -299,9 +297,74 @@ export default function ChatView() { |
| 299 | 297 | sandboxMode: "workspace-write", |
| 300 | 298 | } as const); |
| 301 | 299 | const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null; |
| 302 | | const gitActionsDisabled = |
| 300 | const gitBaseDisabled = |
| 303 | 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 | 369 | useEffect(() => { |
| 307 | 370 | latestGitCwdRef.current = gitCwd; |
| ... | ... | @@ -809,15 +872,13 @@ export default function ChatView() { |
| 809 | 872 | <p className="px-2 pb-1 text-[10px] uppercase tracking-[0.1em] text-muted-foreground/60"> |
| 810 | 873 | Git actions |
| 811 | 874 | </p> |
| 812 | | {GIT_ACTION_ITEMS.map((item) => { |
| 813 | | const disabled = |
| 814 | | item.id === "commit" ? gitActionsDisabled : pushActionDisabled; |
| 875 | {gitActionMenuItems.map((item) => { |
| 815 | 876 | return ( |
| 816 | 877 | <button |
| 817 | 878 | key={item.id} |
| 818 | 879 | type="button" |
| 819 | 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 | 882 | onClick={() => { |
| 822 | 883 | void runGitAction(item.id); |
| 823 | 884 | }} |
| ... | ... | @@ -831,6 +892,15 @@ export default function ChatView() { |
| 831 | 892 | Detached HEAD: push and PR actions are unavailable. |
| 832 | 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 | 904 | </div> |
| 835 | 905 | )} |
| 836 | 906 | </div> |
| ... | ... | @@ -858,13 +928,13 @@ export default function ChatView() { |
| 858 | 928 | )} |
| 859 | 929 | |
| 860 | 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 | 932 | {gitActionError} |
| 863 | 933 | </div> |
| 864 | 934 | )} |
| 865 | 935 | |
| 866 | 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 | 938 | {gitActionNotice} |
| 869 | 939 | </div> |
| 870 | 940 | )} |