Files
kandev/apps/web/components/task-create-dialog-branch-utils.ts
João Salavisa 5257dfbaab fix(web): close the i18n coverage gaps in the non-JSX scanner and locale catalogs (#2727)
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 23:03:29 +00:00

96 lines
3.3 KiB
TypeScript

/**
* Pure helpers for the branch chip in the task-create dialog.
*
* Extracted from `task-create-dialog-repo-chips.tsx` to keep that file under
* the 600-line cap. Tested directly in `task-create-dialog-branch-utils.test.ts`.
*/
import { t } from "@/lib/i18n";
/**
* What the picked branch means for this row.
*
* This used to be the prefix STRING itself: `computeBranchPrefix` returned
* `"will switch to: "` and `computeBranchTooltip` matched it with `===`. That
* made the qualifier both display copy and a comparison token, so translating
* it would have silently collapsed every tooltip to the generic fallback in
* every non-English locale. The discriminant carries the meaning; the copy
* hangs off it.
*/
export type BranchIntent = "none" | "current" | "switch" | "from";
/**
* Decide what the picked branch means, so the chip can qualify it.
* "current" — local exec, picked branch matches the current checkout
* (also covers detached HEAD where the "branch" is the short SHA
* returned by the backend).
* "switch" — local exec, picked branch differs from current. Surfaces the
* destructive `git checkout` decision the backend makes on submit.
* "from" — fork mode (forking a new branch off this base) or non-local
* executors (worktree base).
* "none" — no value yet, so the chip's placeholder ("branch") does not get
* a misleading prefix.
*/
export function computeBranchIntent({
isLocalExecutor,
rowBranch,
currentLocalBranch,
freshBranchEnabled,
}: {
isLocalExecutor: boolean;
rowBranch: string;
currentLocalBranch: string;
freshBranchEnabled: boolean;
}): BranchIntent {
if (!rowBranch) return "none";
if (freshBranchEnabled) return "from";
if (isLocalExecutor) {
if (currentLocalBranch && rowBranch === currentLocalBranch) return "current";
return "switch";
}
return "from";
}
const PREFIX_KEYS: Record<Exclude<BranchIntent, "none">, string> = {
current: "task:branchPrefixCurrent",
switch: "task:branchPrefixWillSwitchTo",
from: "task:branchPrefixFrom",
};
/** Muted text shown before the branch value, or "" when there is nothing to qualify. */
export function computeBranchPrefix(intent: BranchIntent): string {
return intent === "none" ? "" : t(PREFIX_KEYS[intent]);
}
const TOOLTIP_KEYS: Record<Exclude<BranchIntent, "none">, string> = {
current: "task:branchTooltipCurrent",
switch: "task:branchTooltipWillSwitchTo",
from: "task:branchTooltipFrom",
};
/**
* Branch chip hover tooltip, matched to the intent so the explanation lines up
* with the qualifier the chip is showing.
*/
export function computeBranchTooltip(intent: BranchIntent | undefined): string {
if (!intent || intent === "none") return t("task:branchTooltipGeneric");
return t(TOOLTIP_KEYS[intent]);
}
export function computeBranchDisabledReason({
branchLocked,
hasRepo,
branchesLoading,
optionCount,
}: {
branchLocked: boolean;
hasRepo: boolean;
branchesLoading: boolean;
optionCount: number;
}): string | undefined {
if (branchLocked) return t("task:branchDisabledLocalExecutor");
if (!hasRepo) return t("task:branchDisabledSelectRepositoryFirst");
if (branchesLoading) return t("task:loadingBranches");
if (optionCount === 0) return t("task:branchDisabledNoBranches");
return undefined;
}