From 1c0f5c0e92f5e252d94e8fc89f233b8fe8fdd6e2 Mon Sep 17 00:00:00 2001 From: Michael Matloka Date: Mon, 27 Apr 2026 14:21:12 +0200 Subject: [PATCH 1/3] feat(code): GitHub inbox banner connect flow and Button loading state Use project-scoped GitHub integration for banner copy, call github/start from the API with connect_from=posthog_code, and fall back to Personal integrations in the browser. Add startGithubUserIntegrationConnect on PostHogAPIClient; support loading + spinner on shared Button with an in-flight guard on the banner. Point main GitHub authorize next step at /account-connected/github-login for PostHog web companion behavior. --- .../services/github-integration/service.ts | 2 +- apps/code/src/renderer/api/posthogClient.ts | 34 +++++++ .../inbox/components/InboxSignalsTab.tsx | 52 +++++++++- .../inbox/components/InboxSourcesDialog.tsx | 4 +- .../list/GitHubConnectionBanner.tsx | 98 +++++++++++++++---- .../sections/GitHubIntegrationSection.tsx | 14 ++- 6 files changed, 180 insertions(+), 24 deletions(-) diff --git a/apps/code/src/main/services/github-integration/service.ts b/apps/code/src/main/services/github-integration/service.ts index 2361b94ab..500df9b02 100644 --- a/apps/code/src/main/services/github-integration/service.ts +++ b/apps/code/src/main/services/github-integration/service.ts @@ -61,7 +61,7 @@ export class GitHubIntegrationService extends TypedEventEmitter { try { const cloudUrl = getCloudUrlFromRegion(region); - const nextPath = `/account/social-connected?provider=github&project_id=${projectId}&connect_from=posthog_code`; + const nextPath = `/account-connected/github-login?provider=github&project_id=${projectId}&connect_from=posthog_code`; const authorizeUrl = `${cloudUrl}/api/environments/${projectId}/integrations/authorize/?kind=github&next=${encodeURIComponent(nextPath)}`; this.clearFlowTimeout(); diff --git a/apps/code/src/renderer/api/posthogClient.ts b/apps/code/src/renderer/api/posthogClient.ts index a7f6b141b..f660aea4b 100644 --- a/apps/code/src/renderer/api/posthogClient.ts +++ b/apps/code/src/renderer/api/posthogClient.ts @@ -558,6 +558,40 @@ export class PostHogAPIClient { return data.github_login; } + /** + * `POST .../integrations/github/start/`. Optional `teamId` matches app project when session `current_team` differs. + */ + async startGithubUserIntegrationConnect(teamId?: number): Promise<{ + install_url: string; + connect_flow?: "oauth_authorize" | "app_install"; + }> { + const id = teamId ?? (await this.getTeamId()); + const urlPath = `/api/users/@me/integrations/github/start/`; + const url = new URL(`${this.api.baseUrl}${urlPath}`); + const response = await this.api.fetcher.fetch({ + method: "post", + url, + path: urlPath, + overrides: { + body: JSON.stringify({ team_id: id, connect_from: "posthog_code" }), + }, + }); + if (!response.ok) { + const err = (await response.json().catch(() => ({}))) as { + detail?: unknown; + }; + const detail = + typeof err.detail === "string" + ? err.detail + : "Failed to start GitHub connection"; + throw new Error(detail); + } + return (await response.json()) as { + install_url: string; + connect_flow?: "oauth_authorize" | "app_install"; + }; + } + async switchOrganization(orgId: string): Promise { await this.api.patch("/api/users/{uuid}/", { path: { uuid: "@me" }, diff --git a/apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx b/apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx index 522cc81b4..1053cc46a 100644 --- a/apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx +++ b/apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx @@ -24,7 +24,10 @@ import { isReportUpForReview, } from "@features/inbox/utils/filterReports"; import { INBOX_REFETCH_INTERVAL_MS } from "@features/inbox/utils/inboxConstants"; -import { useRepositoryIntegration } from "@hooks/useIntegrations"; +import { + useIntegrations, + useRepositoryIntegration, +} from "@hooks/useIntegrations"; import { Box, Flex, ScrollArea } from "@radix-ui/themes"; import type { SignalReportsQueryParams } from "@shared/types"; import { useNavigationStore } from "@stores/navigationStore"; @@ -55,7 +58,15 @@ export function InboxSignalsTab() { const { hasGithubIntegration } = useRepositoryIntegration(); // ── Signal source configs ─────────────────────────────────────────────── - const { data: signalSourceConfigs } = useSignalSourceConfigs(); + const { data: signalSourceConfigs, isPending: signalSourceConfigsPending } = + useSignalSourceConfigs(); + const { isPending: integrationsPending, data: integrationsData } = + useIntegrations(); + /** Matches store-backed `hasGithubIntegration`, but uses query data so there is no lag behind the `useIntegrations` → Zustand sync effect. */ + const hasGithubIntegrationFromQuery = useMemo( + () => integrationsData?.some((i) => i.kind === "github") ?? false, + [integrationsData], + ); const hasSignalSources = signalSourceConfigs?.some((c) => c.enabled) ?? false; const enabledProducts = useMemo(() => { const seen = new Set(); @@ -78,6 +89,9 @@ export function InboxSignalsTab() { const isInboxView = useNavigationStore((s) => s.view.type === "inbox"); const inboxPollingActive = windowFocused && isInboxView; + const inboxSourcesPrerequisitesLoaded = + !integrationsPending && !signalSourceConfigsPending; + // ── Data fetching ─────────────────────────────────────────────────────── useInboxAvailableSuggestedReviewers({ enabled: isInboxView, @@ -121,6 +135,40 @@ export function InboxSignalsTab() { staleTime: inboxPollingActive ? INBOX_REFETCH_INTERVAL_MS : 12_000, }); + const didAutoOpenSourcesDialogThisInboxVisitRef = useRef(false); + + useEffect(() => { + if (!isInboxView) { + didAutoOpenSourcesDialogThisInboxVisitRef.current = false; + return; + } + if (!inboxSourcesPrerequisitesLoaded || isLoading || error != null) { + return; + } + if (totalCount <= 0) { + return; + } + const needsSourcesOrGithubSetup = + !hasSignalSources || !hasGithubIntegrationFromQuery; + if (!needsSourcesOrGithubSetup) { + return; + } + if (didAutoOpenSourcesDialogThisInboxVisitRef.current) { + return; + } + didAutoOpenSourcesDialogThisInboxVisitRef.current = true; + setSourcesDialogOpen(true); + }, [ + isInboxView, + inboxSourcesPrerequisitesLoaded, + isLoading, + error, + totalCount, + hasSignalSources, + hasGithubIntegrationFromQuery, + setSourcesDialogOpen, + ]); + const reports = useMemo( () => filterReportsBySearch(allReports, searchQuery), [allReports, searchQuery], diff --git a/apps/code/src/renderer/features/inbox/components/InboxSourcesDialog.tsx b/apps/code/src/renderer/features/inbox/components/InboxSourcesDialog.tsx index 1f1bd1bad..ac740a383 100644 --- a/apps/code/src/renderer/features/inbox/components/InboxSourcesDialog.tsx +++ b/apps/code/src/renderer/features/inbox/components/InboxSourcesDialog.tsx @@ -17,10 +17,10 @@ export function InboxSourcesDialog({ }: InboxSourcesDialogProps) { return ( - + - Signal sources + Inbox configuration diff --git a/apps/code/src/renderer/features/settings/components/sections/GitHubIntegrationSection.tsx b/apps/code/src/renderer/features/settings/components/sections/GitHubIntegrationSection.tsx index 21fcd1b62..7b498abbd 100644 --- a/apps/code/src/renderer/features/settings/components/sections/GitHubIntegrationSection.tsx +++ b/apps/code/src/renderer/features/settings/components/sections/GitHubIntegrationSection.tsx @@ -56,6 +56,18 @@ export function GitHubIntegrationSection({ } }, [hasGithubIntegration, connecting, stopPolling]); + // Fallback for when the `posthog-code://integration` deep link from PostHog Cloud + // never makes it back to the app (browser blocked the protocol prompt, focus didn't + // return cleanly, etc.). The integrations query has a 5-minute staleTime so the + // global `refetchOnWindowFocus: true` won't refetch it on its own — invalidate + // explicitly while a connect flow is in flight. + useEffect(() => { + if (!connecting) return; + const handleFocus = () => invalidateIntegrations(); + window.addEventListener("focus", handleFocus); + return () => window.removeEventListener("focus", handleFocus); + }, [connecting, invalidateIntegrations]); + useGitHubIntegrationCallback({ onSuccess: () => { stopPolling(); @@ -111,7 +123,7 @@ export function GitHubIntegrationSection({ - Code access + Project-level code access {hasGithubIntegration && !isLoadingRepos && From c29e263d27e76cab8c248943bf51b22f1b7d4087 Mon Sep 17 00:00:00 2001 From: Michael Matloka Date: Tue, 28 Apr 2026 19:35:52 +0200 Subject: [PATCH 2/3] Satisfy semgrep --- .github/workflows/code-release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code-release.yml b/.github/workflows/code-release.yml index 91e7ffd7c..d3f6b93e8 100644 --- a/.github/workflows/code-release.yml +++ b/.github/workflows/code-release.yml @@ -167,8 +167,10 @@ jobs: - name: Extract version from tag id: version shell: pwsh + env: + GITHUB_REF: ${{ github.ref }} run: | - $tagVersion = "${{ github.ref }}" -replace "refs/tags/v", "" + $tagVersion = "$env:GITHUB_REF" -replace "refs/tags/v", "" echo "Version: $tagVersion" echo "version=$tagVersion" >> $env:GITHUB_OUTPUT From 877f8e236751a562bbe3d07c459111cd7ad9b6fe Mon Sep 17 00:00:00 2001 From: Michael Matloka Date: Wed, 29 Apr 2026 16:58:00 +0200 Subject: [PATCH 3/3] Make GH connection state more prominent --- .../list/GitHubConnectionBanner.tsx | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/code/src/renderer/features/inbox/components/list/GitHubConnectionBanner.tsx b/apps/code/src/renderer/features/inbox/components/list/GitHubConnectionBanner.tsx index 11648fffc..af78919c9 100644 --- a/apps/code/src/renderer/features/inbox/components/list/GitHubConnectionBanner.tsx +++ b/apps/code/src/renderer/features/inbox/components/list/GitHubConnectionBanner.tsx @@ -68,9 +68,11 @@ export function GitHubConnectionBanner() { return null; } - const label = hasGithubForProject - ? "Connect your GitHub profile to highlight what's relevant to you" - : "Connect your GitHub repo(s) to highlight what's relevant to you"; + const label = connecting + ? "Waiting for GitHub connection to complete in browser…" + : hasGithubForProject + ? "Connect your GitHub profile to highlight what's relevant to you" + : "Connect your GitHub repo(s) to highlight what's relevant to you"; return (
@@ -139,12 +141,17 @@ export function GitHubConnectionBanner() { }} > {connecting ? ( - + <> + + {label} + ) : ( - + <> + + {label} + + )} - {label} -
);