diff --git a/apps/web/src/components/GitActionsControl.logic.test.ts b/apps/web/src/components/GitActionsControl.logic.test.ts index 0e1583e1bd6db0deefb2ae2e2ceafd25ffd9a68d..42ab3ebca88c34871b76d16dcbdf78726c19476f 100644 --- a/apps/web/src/components/GitActionsControl.logic.test.ts +++ b/apps/web/src/components/GitActionsControl.logic.test.ts @@ -4,6 +4,7 @@ import { buildMenuItems, requiresDefaultBranchConfirmation, resolveQuickAction, + summarizeGitResult, } from "./GitActionsControl.logic"; function status(overrides: Partial = {}): GitStatusResult { @@ -577,3 +578,93 @@ describe("requiresDefaultBranchConfirmation", () => { assert.isFalse(requiresDefaultBranchConfirmation("commit_push", false)); }); }); + +describe("summarizeGitResult", () => { + it("returns commit-focused toast for commit action", () => { + const result = summarizeGitResult({ + action: "commit", + commit: { + status: "created", + commitSha: "0123456789abcdef", + subject: "feat: add optimistic UI for git action button", + }, + push: { status: "skipped_not_requested" }, + pr: { status: "skipped_not_requested" }, + }); + + assert.deepEqual(result, { + title: "Committed 0123456", + description: "feat: add optimistic UI for git action button", + }); + }); + + it("returns push-focused toast for push action", () => { + const result = summarizeGitResult({ + action: "commit_push", + commit: { + status: "created", + commitSha: "abcdef0123456789", + subject: "fix: tighten quick action tooltip hover handling", + }, + push: { + status: "pushed", + branch: "foo", + upstreamBranch: "origin/foo", + }, + pr: { status: "skipped_not_requested" }, + }); + + assert.deepEqual(result, { + title: "Pushed abcdef0 to foo", + description: "fix: tighten quick action tooltip hover handling", + }); + }); + + it("returns PR-focused toast for created PR action", () => { + const result = summarizeGitResult({ + action: "commit_push_pr", + commit: { + status: "created", + commitSha: "89abcdef01234567", + subject: "feat: ship github shortcuts", + }, + push: { + status: "pushed", + branch: "foo", + }, + pr: { + status: "created", + number: 42, + title: "feat: ship github shortcuts and improve PR CTA in success toast", + }, + }); + + assert.deepEqual(result, { + title: "Created PR #42", + description: "feat: ship github shortcuts and improve PR CTA in success toast", + }); + }); + + it("truncates long description text", () => { + const result = summarizeGitResult({ + action: "commit_push_pr", + commit: { + status: "created", + commitSha: "89abcdef01234567", + subject: "short subject", + }, + push: { status: "pushed", branch: "foo" }, + pr: { + status: "created", + number: 99, + title: + "feat: this title is intentionally extremely long so we can validate that toast descriptions are truncated with an ellipsis suffix", + }, + }); + + assert.deepEqual(result, { + title: "Created PR #99", + description: "feat: this title is intentionally extremely long so we can validate t...", + }); + }); +}); diff --git a/apps/web/src/components/GitActionsControl.logic.ts b/apps/web/src/components/GitActionsControl.logic.ts index 1801ef43bdf71b76ba485f1a6d8a7daca13a62db..3debfc121b03bbcd2727d7517943781776ce9283 100644 --- a/apps/web/src/components/GitActionsControl.logic.ts +++ b/apps/web/src/components/GitActionsControl.logic.ts @@ -21,28 +21,51 @@ export interface GitQuickAction { hint?: string; } -export function describeGitResult(result: GitRunStackedActionResult): string { - const parts: string[] = []; +const SHORT_SHA_LENGTH = 7; +const TOAST_DESCRIPTION_MAX = 72; - if (result.commit.status === "created") { - parts.push(result.commit.subject ?? "Committed changes"); +function shortenSha(sha: string | undefined): string | null { + if (!sha) return null; + return sha.slice(0, SHORT_SHA_LENGTH); +} + +function truncateText(value: string | undefined, maxLength = TOAST_DESCRIPTION_MAX): string | undefined { + if (!value) return undefined; + if (value.length <= maxLength) return value; + if (maxLength <= 3) return "...".slice(0, maxLength); + return `${value.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`; +} + +export function summarizeGitResult( + result: GitRunStackedActionResult, +): { title: string; description?: string } { + const withDescription = (title: string, description: string | undefined) => + description ? { title, description } : { title }; + + if (result.pr.status === "created" || result.pr.status === "opened_existing") { + const prNumber = result.pr.number ? ` #${result.pr.number}` : ""; + const title = `${result.pr.status === "created" ? "Created PR" : "Opened PR"}${prNumber}`; + return withDescription(title, truncateText(result.pr.title)); } if (result.push.status === "pushed") { - parts.push( - result.push.upstreamBranch - ? `Pushed to ${result.push.upstreamBranch}` - : "Pushed", + const shortSha = shortenSha(result.commit.commitSha); + const branch = result.push.branch ?? result.push.upstreamBranch; + const pushedCommitPart = shortSha ? ` ${shortSha}` : ""; + const branchPart = branch ? ` to ${branch}` : ""; + return withDescription( + `Pushed${pushedCommitPart}${branchPart}`, + truncateText(result.commit.subject), ); } - if (result.pr.status === "created") { - parts.push(result.pr.number ? `Created PR #${result.pr.number}` : "Created PR"); - } else if (result.pr.status === "opened_existing") { - parts.push(result.pr.number ? `PR #${result.pr.number}` : "Opened existing PR"); + if (result.commit.status === "created") { + const shortSha = shortenSha(result.commit.commitSha); + const title = shortSha ? `Committed ${shortSha}` : "Committed changes"; + return withDescription(title, truncateText(result.commit.subject)); } - return parts.join(" ยท ") || "No changes needed."; + return { title: "Done" }; } export function buildMenuItems( diff --git a/apps/web/src/components/GitActionsControl.tsx b/apps/web/src/components/GitActionsControl.tsx index 8d0e19658e60d4f7ebafaef07140ea85a53265b7..639053b3f064c0832ed48febc1f134a998774348 100644 --- a/apps/web/src/components/GitActionsControl.tsx +++ b/apps/web/src/components/GitActionsControl.tsx @@ -13,9 +13,9 @@ import { type GitActionMenuItem, type GitDialogAction, type GitQuickAction, - describeGitResult, requiresDefaultBranchConfirmation, resolveQuickAction, + summarizeGitResult, } from "./GitActionsControl.logic"; import { Badge } from "~/components/ui/badge"; import { Button } from "~/components/ui/button"; @@ -282,6 +282,7 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp try { const result = await promise; stopProgressUpdates(); + const resultToast = summarizeGitResult(result); const prUrl = result.pr.url ?? gitStatus?.openPr?.url; const shouldOfferPushCta = action === "commit" && result.commit.status === "created"; @@ -292,8 +293,8 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp toastManager.update(progressToastId, { type: "success", - title: "Done", - description: describeGitResult(result), + title: resultToast.title, + description: resultToast.description, timeout: 10_000, ...(shouldOfferPushCta ? {