History

Refine git action success toasts with contextual summaries

- replace generic success text with action-specific toast titles/descriptions - include short SHA/branch details for push and commit outcomes - add tests for PR/commit/push summaries and description truncation

Julius Marminge committed 9 months ago

Commit 5caae991 parent beb92013 files · +131 −16Browse files

Unified · Split

3 files
  1. apps/web/src/components/GitActionsControl.logic.test.ts +91 −0
  2. apps/web/src/components/GitActionsControl.logic.ts +36 −13
  3. apps/web/src/components/GitActionsControl.tsx +4 −3
apps/web/src/components/GitActionsControl.logic.test.ts+91−0
@@ -4,6 +4,7 @@ import { · 3 unmodified lines
44 buildMenuItems,
55 requiresDefaultBranchConfirmation,
66 resolveQuickAction,
7+ summarizeGitResult,
78 } from "./GitActionsControl.logic";
89
910 function status(overrides: Partial<GitStatusResult> = {}): GitStatusResult {
@@ -577,3 +578,93 @@ describe("requiresDefaultBranchConfirmation", () => { · 567 unmodified lines
577578 assert.isFalse(requiresDefaultBranchConfirmation("commit_push", false));
578579 });
579580 });
581+
582+describe("summarizeGitResult", () => {
583+ it("returns commit-focused toast for commit action", () => {
584+ const result = summarizeGitResult({
585+ action: "commit",
586+ commit: {
587+ status: "created",
588+ commitSha: "0123456789abcdef",
589+ subject: "feat: add optimistic UI for git action button",
590+ },
591+ push: { status: "skipped_not_requested" },
592+ pr: { status: "skipped_not_requested" },
593+ });
594+
595+ assert.deepEqual(result, {
596+ title: "Committed 0123456",
597+ description: "feat: add optimistic UI for git action button",
598+ });
599+ });
600+
601+ it("returns push-focused toast for push action", () => {
602+ const result = summarizeGitResult({
603+ action: "commit_push",
604+ commit: {
605+ status: "created",
606+ commitSha: "abcdef0123456789",
607+ subject: "fix: tighten quick action tooltip hover handling",
608+ },
609+ push: {
610+ status: "pushed",
611+ branch: "foo",
612+ upstreamBranch: "origin/foo",
613+ },
614+ pr: { status: "skipped_not_requested" },
615+ });
616+
617+ assert.deepEqual(result, {
618+ title: "Pushed abcdef0 to foo",
619+ description: "fix: tighten quick action tooltip hover handling",
620+ });
621+ });
622+
623+ it("returns PR-focused toast for created PR action", () => {
624+ const result = summarizeGitResult({
625+ action: "commit_push_pr",
626+ commit: {
627+ status: "created",
628+ commitSha: "89abcdef01234567",
629+ subject: "feat: ship github shortcuts",
630+ },
631+ push: {
632+ status: "pushed",
633+ branch: "foo",
634+ },
635+ pr: {
636+ status: "created",
637+ number: 42,
638+ title: "feat: ship github shortcuts and improve PR CTA in success toast",
639+ },
640+ });
641+
642+ assert.deepEqual(result, {
643+ title: "Created PR #42",
644+ description: "feat: ship github shortcuts and improve PR CTA in success toast",
645+ });
646+ });
647+
648+ it("truncates long description text", () => {
649+ const result = summarizeGitResult({
650+ action: "commit_push_pr",
651+ commit: {
652+ status: "created",
653+ commitSha: "89abcdef01234567",
654+ subject: "short subject",
655+ },
656+ push: { status: "pushed", branch: "foo" },
657+ pr: {
658+ status: "created",
659+ number: 99,
660+ title:
661+ "feat: this title is intentionally extremely long so we can validate that toast descriptions are truncated with an ellipsis suffix",
662+ },
663+ });
664+
665+ assert.deepEqual(result, {
666+ title: "Created PR #99",
667+ description: "feat: this title is intentionally extremely long so we can validate t...",
668+ });
669+ });
670+});
apps/web/src/components/GitActionsControl.logic.ts+36−13
@@ -21,28 +21,51 @@ export interface GitQuickAction { · 20 unmodified lines
2121 hint?: string;
2222 }
2323
24−export function describeGitResult(result: GitRunStackedActionResult): string {
25− const parts: string[] = [];
24+const SHORT_SHA_LENGTH = 7;
25+const TOAST_DESCRIPTION_MAX = 72;
2626
27− if (result.commit.status === "created") {
28− parts.push(result.commit.subject ?? "Committed changes");
27+function shortenSha(sha: string | undefined): string | null {
28+ if (!sha) return null;
29+ return sha.slice(0, SHORT_SHA_LENGTH);
30+}
31+
32+function truncateText(value: string | undefined, maxLength = TOAST_DESCRIPTION_MAX): string | undefined {
33+ if (!value) return undefined;
34+ if (value.length <= maxLength) return value;
35+ if (maxLength <= 3) return "...".slice(0, maxLength);
36+ return `${value.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
37+}
38+
39+export function summarizeGitResult(
40+ result: GitRunStackedActionResult,
41+): { title: string; description?: string } {
42+ const withDescription = (title: string, description: string | undefined) =>
43+ description ? { title, description } : { title };
44+
45+ if (result.pr.status === "created" || result.pr.status === "opened_existing") {
46+ const prNumber = result.pr.number ? ` #${result.pr.number}` : "";
47+ const title = `${result.pr.status === "created" ? "Created PR" : "Opened PR"}${prNumber}`;
48+ return withDescription(title, truncateText(result.pr.title));
2949 }
3050
3151 if (result.push.status === "pushed") {
32− parts.push(
33− result.push.upstreamBranch
34− ? `Pushed to ${result.push.upstreamBranch}`
35− : "Pushed",
52+ const shortSha = shortenSha(result.commit.commitSha);
53+ const branch = result.push.branch ?? result.push.upstreamBranch;
54+ const pushedCommitPart = shortSha ? ` ${shortSha}` : "";
55+ const branchPart = branch ? ` to ${branch}` : "";
56+ return withDescription(
57+ `Pushed${pushedCommitPart}${branchPart}`,
58+ truncateText(result.commit.subject),
3659 );
3760 }
3861
39− if (result.pr.status === "created") {
40− parts.push(result.pr.number ? `Created PR #${result.pr.number}` : "Created PR");
41− } else if (result.pr.status === "opened_existing") {
42− parts.push(result.pr.number ? `PR #${result.pr.number}` : "Opened existing PR");
62+ if (result.commit.status === "created") {
63+ const shortSha = shortenSha(result.commit.commitSha);
64+ const title = shortSha ? `Committed ${shortSha}` : "Committed changes";
65+ return withDescription(title, truncateText(result.commit.subject));
4366 }
4467
45− return parts.join(" · ") || "No changes needed.";
68+ return { title: "Done" };
4669 }
4770
4871 export function buildMenuItems(
apps/web/src/components/GitActionsControl.tsx+4−3
@@ -13,9 +13,9 @@ import { · 12 unmodified lines
1313 type GitActionMenuItem,
1414 type GitDialogAction,
1515 type GitQuickAction,
16− describeGitResult,
1716 requiresDefaultBranchConfirmation,
1817 resolveQuickAction,
18+ summarizeGitResult,
1919 } from "./GitActionsControl.logic";
2020 import { Badge } from "~/components/ui/badge";
2121 import { Button } from "~/components/ui/button";
@@ -282,6 +282,7 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp · 260 unmodified lines
282282 try {
283283 const result = await promise;
284284 stopProgressUpdates();
285+ const resultToast = summarizeGitResult(result);
285286
286287 const prUrl = result.pr.url ?? gitStatus?.openPr?.url;
287288 const shouldOfferPushCta = action === "commit" && result.commit.status === "created";
@@ -292,8 +293,8 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp · 4 unmodified lines
292293
293294 toastManager.update(progressToastId, {
294295 type: "success",
295− title: "Done",
296− description: describeGitResult(result),
296+ title: resultToast.title,
297+ description: resultToast.description,
297298 timeout: 10_000,
298299 ...(shouldOfferPushCta
299300 ? {