fix(sidebar): fix PR hint for fork PRs, remove badge styling, add hover effect
- Replace REST API PR lookup with `gh pr view` which handles fork PRs - Fix state comparison to use lowercase (matching server output) - Remove pill/badge styling, show plain icon + #number - Add brighter hover colors and underline for clickability hint
This commit is contained in:
@@ -104,6 +104,11 @@ const GITHUB_PR_STATE_COLORS: Record<PrHint["state"], string> = {
|
||||
merged: "#a371f7",
|
||||
closed: "#f85149",
|
||||
};
|
||||
const GITHUB_PR_STATE_COLORS_HOVER: Record<PrHint["state"], string> = {
|
||||
open: "#56d364",
|
||||
merged: "#bc8cff",
|
||||
closed: "#ff7b72",
|
||||
};
|
||||
|
||||
interface SidebarWorkspaceListProps {
|
||||
projects: SidebarProjectEntry[];
|
||||
@@ -164,6 +169,9 @@ interface WorkspaceRowInnerProps {
|
||||
|
||||
function WorkspacePrBadge({ hint }: { hint: PrHint }) {
|
||||
const color = GITHUB_PR_STATE_COLORS[hint.state];
|
||||
const hoverColor = GITHUB_PR_STATE_COLORS_HOVER[hint.state];
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const activeColor = isHovered ? hoverColor : color;
|
||||
|
||||
const handlePressIn = useCallback((event: GestureResponderEvent) => {
|
||||
event.stopPropagation();
|
||||
@@ -184,17 +192,22 @@ function WorkspacePrBadge({ hint }: { hint: PrHint }) {
|
||||
hitSlop={4}
|
||||
onPressIn={handlePressIn}
|
||||
onPress={handlePress}
|
||||
onPointerEnter={() => setIsHovered(true)}
|
||||
onPointerLeave={() => setIsHovered(false)}
|
||||
style={({ pressed }) => [
|
||||
styles.workspacePrBadge,
|
||||
{
|
||||
borderColor: color,
|
||||
backgroundColor: `${color}1a`,
|
||||
},
|
||||
pressed && styles.workspacePrBadgePressed,
|
||||
]}
|
||||
>
|
||||
<GitPullRequest size={12} color={color} />
|
||||
<Text style={[styles.workspacePrBadgeText, { color }]} numberOfLines={1}>
|
||||
<GitPullRequest size={12} color={activeColor} />
|
||||
<Text
|
||||
style={[
|
||||
styles.workspacePrBadgeText,
|
||||
{ color: activeColor },
|
||||
isHovered && styles.workspacePrBadgeTextHovered,
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
#{hint.number}
|
||||
</Text>
|
||||
</Pressable>
|
||||
@@ -2160,10 +2173,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[1],
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
paddingVertical: 3,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
borderWidth: 1,
|
||||
},
|
||||
workspacePrBadgePressed: {
|
||||
opacity: 0.82,
|
||||
@@ -2173,6 +2182,9 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontWeight: theme.fontWeight.medium,
|
||||
lineHeight: 14,
|
||||
},
|
||||
workspacePrBadgeTextHovered: {
|
||||
textDecorationLine: "underline",
|
||||
},
|
||||
workspaceCreatingText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
|
||||
@@ -51,9 +51,9 @@ function selectWorkspacePrHint(payload: CheckoutPrStatusPayload): PrHint | null
|
||||
return {
|
||||
url: status.url,
|
||||
number,
|
||||
state: status.isMerged || status.state === "MERGED"
|
||||
state: status.isMerged || status.state === "merged"
|
||||
? "merged"
|
||||
: status.state === "OPEN"
|
||||
: status.state === "open"
|
||||
? "open"
|
||||
: "closed",
|
||||
};
|
||||
|
||||
@@ -1745,61 +1745,6 @@ async function resolveGitHubRepo(cwd: string): Promise<string | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function listPullRequestsForHead(options: {
|
||||
cwd: string;
|
||||
repo: string;
|
||||
owner: string;
|
||||
head: string;
|
||||
state: "open" | "closed";
|
||||
}): Promise<any[]> {
|
||||
const { stdout } = await execFileAsync(
|
||||
"gh",
|
||||
[
|
||||
"api",
|
||||
`repos/${options.repo}/pulls`,
|
||||
"-X",
|
||||
"GET",
|
||||
"-F",
|
||||
`head=${options.owner}:${options.head}`,
|
||||
"-F",
|
||||
`state=${options.state}`,
|
||||
],
|
||||
{ cwd: options.cwd },
|
||||
);
|
||||
const parsed = JSON.parse(stdout.trim());
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
}
|
||||
|
||||
function buildPullRequestStatus(current: any, fallbackHead: string): PullRequestStatus | null {
|
||||
if (!current || typeof current !== "object") {
|
||||
return null;
|
||||
}
|
||||
const url = current.html_url ?? current.url;
|
||||
const title = current.title;
|
||||
if (typeof url !== "string" || typeof title !== "string" || !url || !title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mergedAt =
|
||||
typeof current.merged_at === "string" && current.merged_at.trim().length > 0
|
||||
? current.merged_at
|
||||
: null;
|
||||
const state =
|
||||
mergedAt !== null
|
||||
? "merged"
|
||||
: typeof current.state === "string" && current.state.trim().length > 0
|
||||
? current.state
|
||||
: "";
|
||||
|
||||
return {
|
||||
url,
|
||||
title,
|
||||
state,
|
||||
baseRefName: current.base?.ref ?? "",
|
||||
headRefName: current.head?.ref ?? fallbackHead,
|
||||
isMerged: mergedAt !== null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createPullRequest(
|
||||
cwd: string,
|
||||
@@ -1844,9 +1789,8 @@ export async function createPullRequest(
|
||||
|
||||
export async function getPullRequestStatus(cwd: string): Promise<PullRequestStatusResult> {
|
||||
await requireGitRepo(cwd);
|
||||
const repo = await resolveGitHubRepo(cwd);
|
||||
const head = await getCurrentBranch(cwd);
|
||||
if (!repo || !head) {
|
||||
if (!head) {
|
||||
return {
|
||||
status: null,
|
||||
githubFeaturesEnabled: false,
|
||||
@@ -1860,69 +1804,51 @@ export async function getPullRequestStatus(cwd: string): Promise<PullRequestStat
|
||||
githubFeaturesEnabled: false,
|
||||
};
|
||||
}
|
||||
const owner = repo.split("/")[0];
|
||||
let openPulls: any[];
|
||||
try {
|
||||
openPulls = await listPullRequestsForHead({
|
||||
cwd,
|
||||
repo,
|
||||
owner,
|
||||
head,
|
||||
state: "open",
|
||||
});
|
||||
const { stdout } = await execFileAsync(
|
||||
"gh",
|
||||
[
|
||||
"pr",
|
||||
"view",
|
||||
"--json",
|
||||
"url,title,state,baseRefName,headRefName,mergedAt",
|
||||
],
|
||||
{ cwd },
|
||||
);
|
||||
const pr = JSON.parse(stdout.trim());
|
||||
if (!pr || typeof pr !== "object" || !pr.url || !pr.title) {
|
||||
return { status: null, githubFeaturesEnabled: true };
|
||||
}
|
||||
const mergedAt =
|
||||
typeof pr.mergedAt === "string" && pr.mergedAt.trim().length > 0
|
||||
? pr.mergedAt
|
||||
: null;
|
||||
const state =
|
||||
mergedAt !== null
|
||||
? "merged"
|
||||
: typeof pr.state === "string" && pr.state.trim().length > 0
|
||||
? pr.state.toLowerCase()
|
||||
: "";
|
||||
return {
|
||||
status: {
|
||||
url: pr.url,
|
||||
title: pr.title,
|
||||
state,
|
||||
baseRefName: pr.baseRefName ?? "",
|
||||
headRefName: pr.headRefName ?? head,
|
||||
isMerged: mergedAt !== null,
|
||||
},
|
||||
githubFeaturesEnabled: true,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isGhAuthError(error)) {
|
||||
return {
|
||||
status: null,
|
||||
githubFeaturesEnabled: false,
|
||||
};
|
||||
return { status: null, githubFeaturesEnabled: false };
|
||||
}
|
||||
// gh pr view exits non-zero when no PR exists for the branch
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
if (message.includes("no pull requests found") || message.includes("Could not resolve")) {
|
||||
return { status: null, githubFeaturesEnabled: true };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
const openPull = openPulls[0] ?? null;
|
||||
const openStatus = buildPullRequestStatus(openPull, head);
|
||||
if (openStatus) {
|
||||
return {
|
||||
status: openStatus,
|
||||
githubFeaturesEnabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
let closedPulls: any[];
|
||||
try {
|
||||
closedPulls = await listPullRequestsForHead({
|
||||
cwd,
|
||||
repo,
|
||||
owner,
|
||||
head,
|
||||
state: "closed",
|
||||
});
|
||||
} catch (error) {
|
||||
if (isGhAuthError(error)) {
|
||||
return {
|
||||
status: null,
|
||||
githubFeaturesEnabled: false,
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
const mergedClosedPull =
|
||||
closedPulls.find(
|
||||
(entry) =>
|
||||
entry &&
|
||||
typeof entry === "object" &&
|
||||
typeof entry.merged_at === "string" &&
|
||||
entry.merged_at.trim().length > 0,
|
||||
) ?? null;
|
||||
const mergedStatus = buildPullRequestStatus(mergedClosedPull, head);
|
||||
if (!mergedStatus) {
|
||||
return {
|
||||
status: null,
|
||||
githubFeaturesEnabled: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: mergedStatus,
|
||||
githubFeaturesEnabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user