Improve GitHub panel toolbar and loading states (#1664)
* feat(app): add toolbar and loading states to the GitHub PR panel Adds a toolbar under the PR title with a refresh button (refreshes git and GitHub status + timeline in one tap), an always-visible View Pull Request button, and a Merge control with a method dropdown that is disabled-with-reason when the PR can't be merged yet. The merge control reuses the existing git-actions policy verbatim via a new buildPrPanelMergeActions selector fed into GitActionsSplitButton, so no merge readiness/squash/auto-merge logic is duplicated. Also replaces the empty-state flash with skeletons: a full-panel skeleton while PR status loads and an activity skeleton while the timeline loads, with empty states shown only when loaded-and-empty and an error state with retry. * refactor(app): tidy PR panel skeleton (i18n, shared pulse, index) Addresses review feedback on the PR panel work: - Localize the skeleton's "Checks" label via t() instead of hardcoding. - Hoist the shared SkeletonPulse + useSkeletonPulse into activity-skeleton so pane-skeleton reuses them instead of duplicating the pulse driver. - Export PullRequestPaneError/Skeleton from the module index and import them through it, matching the module's existing public surface. * test(app): remove mock-based PR pane test * fix(app): simplify PR panel toolbar * fix(app): report PR panel retry failures
This commit is contained in:
@@ -15,9 +15,13 @@ import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
formatPrTabLabel,
|
||||
PullRequestPane,
|
||||
PullRequestPaneError,
|
||||
PullRequestPaneSkeleton,
|
||||
PullRequestTabIcon,
|
||||
usePrPaneData,
|
||||
} from "@/git/pull-request-panel";
|
||||
import { useCheckoutGitActionsStore } from "@/git/actions-store";
|
||||
import type { UsePrPaneDataResult } from "@/git/pull-request-panel/use-data";
|
||||
import {
|
||||
usePanelStore,
|
||||
selectIsFileExplorerOpen,
|
||||
@@ -27,6 +31,7 @@ import {
|
||||
} from "@/stores/panel-store";
|
||||
import { useExplorerSidebarAnimation } from "@/contexts/explorer-sidebar-animation-context";
|
||||
import { useSidebarAnimation } from "@/contexts/sidebar-animation-context";
|
||||
import { useToast } from "@/contexts/toast-context";
|
||||
import { canCloseRightSidebarGesture } from "@/utils/sidebar-animation-state";
|
||||
import { HEADER_INNER_HEIGHT } from "@/constants/layout";
|
||||
import { GitDiffPane } from "@/git/diff-pane";
|
||||
@@ -452,6 +457,7 @@ function ExplorerSidebarContent({
|
||||
}: SidebarContentProps) {
|
||||
const { theme } = useUnistyles();
|
||||
const { t } = useTranslation();
|
||||
const toast = useToast();
|
||||
const padding = useWindowControlsPadding("explorerSidebar");
|
||||
const canQueryPullRequest = isGit && Boolean(workspaceRoot);
|
||||
const prPane = usePrPaneData({
|
||||
@@ -461,11 +467,17 @@ function ExplorerSidebarContent({
|
||||
timelineEnabled: activeTab === "pr" && canQueryPullRequest && isOpen,
|
||||
});
|
||||
const hasPullRequest = prPane.prNumber !== null;
|
||||
const showPrTab = hasPullRequest || (activeTab === "pr" && prPane.isLoading);
|
||||
const requestedTab: ExplorerTab =
|
||||
!isGit && (activeTab === "changes" || activeTab === "pr") ? "files" : activeTab;
|
||||
const resolvedTab: ExplorerTab =
|
||||
requestedTab === "pr" && !hasPullRequest ? "changes" : requestedTab;
|
||||
const resolvedTab: ExplorerTab = requestedTab === "pr" && !showPrTab ? "changes" : requestedTab;
|
||||
const prTabLabel = formatPrTabLabel(prPane.prNumber);
|
||||
const refreshGitActions = useCheckoutGitActionsStore((s) => s.refresh);
|
||||
const handlePrRetry = useCallback(() => {
|
||||
refreshGitActions({ serverId, cwd: workspaceRoot }).catch((error) => {
|
||||
toast.error(error instanceof Error ? error.message : t("workspace.git.diff.failedRefresh"));
|
||||
});
|
||||
}, [refreshGitActions, serverId, t, toast, workspaceRoot]);
|
||||
const workspaceAttachmentScopeKey = useMemo(
|
||||
() => buildWorkspaceAttachmentScopeKey({ serverId, workspaceId, cwd: workspaceRoot }),
|
||||
[serverId, workspaceId, workspaceRoot],
|
||||
@@ -498,7 +510,7 @@ function ExplorerSidebarContent({
|
||||
onTabPress={onTabPress}
|
||||
testID="explorer-tab-files"
|
||||
/>
|
||||
{isGit && hasPullRequest && (
|
||||
{isGit && showPrTab && (
|
||||
<ExplorerTabButton
|
||||
tab="pr"
|
||||
active={resolvedTab === "pr"}
|
||||
@@ -542,12 +554,13 @@ function ExplorerSidebarContent({
|
||||
onOpenFile={onOpenFile}
|
||||
/>
|
||||
)}
|
||||
{resolvedTab === "pr" && prPane.data && (
|
||||
<PullRequestPane
|
||||
{resolvedTab === "pr" && (
|
||||
<PrTabContent
|
||||
serverId={serverId}
|
||||
cwd={workspaceRoot}
|
||||
data={prPane.data}
|
||||
prPane={prPane}
|
||||
workspaceAttachmentScopeKey={workspaceAttachmentScopeKey}
|
||||
onRetry={handlePrRetry}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
@@ -555,6 +568,38 @@ function ExplorerSidebarContent({
|
||||
);
|
||||
}
|
||||
|
||||
interface PrTabContentProps {
|
||||
serverId: string;
|
||||
cwd: string;
|
||||
prPane: UsePrPaneDataResult;
|
||||
workspaceAttachmentScopeKey: string;
|
||||
onRetry: () => void;
|
||||
}
|
||||
|
||||
function PrTabContent({
|
||||
serverId,
|
||||
cwd,
|
||||
prPane,
|
||||
workspaceAttachmentScopeKey,
|
||||
onRetry,
|
||||
}: PrTabContentProps) {
|
||||
if (prPane.data) {
|
||||
return (
|
||||
<PullRequestPane
|
||||
serverId={serverId}
|
||||
cwd={cwd}
|
||||
data={prPane.data}
|
||||
activityLoading={prPane.activityLoading}
|
||||
workspaceAttachmentScopeKey={workspaceAttachmentScopeKey}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (prPane.error) {
|
||||
return <PullRequestPaneError onRetry={onRetry} />;
|
||||
}
|
||||
return <PullRequestPaneSkeleton />;
|
||||
}
|
||||
|
||||
// Static styles for Animated.Views — must NOT use Unistyles dynamic theme to
|
||||
// avoid the "Unable to find node on an unmounted component" crash when Unistyles
|
||||
// tries to patch the native node that Reanimated also manages.
|
||||
|
||||
Reference in New Issue
Block a user