+ );
+}
diff --git a/packages/ui/src/features/inbox/components/onboarding/FakeSlack.tsx b/packages/ui/src/features/inbox/components/onboarding/FakeSlack.tsx
new file mode 100644
index 0000000000..35f0cb1866
--- /dev/null
+++ b/packages/ui/src/features/inbox/components/onboarding/FakeSlack.tsx
@@ -0,0 +1,233 @@
+import { cn } from "@posthog/quill";
+import slackAppLogo from "@posthog/ui/assets/services/posthog-slack-app.png";
+import { playCompletionSound } from "@posthog/ui/utils/sounds";
+import { Flex, Text } from "@radix-ui/themes";
+import type { ReactNode } from "react";
+import { useState } from "react";
+
+/**
+ * High-fidelity, non-interactive Slack stand-ins used in the inbox onboarding
+ * welcome scene. These mimic Slack's chrome — channel header, message gutter,
+ * square avatars, bold name + timestamp, mention pills, and Block Kit-style
+ * message bodies — closely enough that the demo reads as the real thing without
+ * pulling in any live Slack data. Theme tokens are used so it sits naturally in
+ * the app's light or dark surface rather than a hard white Slack panel.
+ */
+
+function SlackSurface({
+ channel,
+ children,
+}: {
+ channel: string;
+ children: ReactNode;
+}) {
+ return (
+
+
+
+ );
+}
+
+export function SlackMention({ name }: { name: string }) {
+ return (
+
+ @{name}
+
+ );
+}
+
+function SlackButton({
+ children,
+ primary = false,
+ onClick,
+}: {
+ children: ReactNode;
+ primary?: boolean;
+ onClick?: () => void;
+}) {
+ return (
+
+ );
+}
+
+/**
+ * Beat 2 preview: the Block Kit report notification PostHog posts to the
+ * dedicated `#posthog-inbox` channel. Mirrors the real backend block layout in
+ * `slack_inbox_notifications.py`: header → meta line → summary → context line →
+ * action buttons.
+ */
+export function SlackReportNotificationPreview() {
+ return (
+
+
+
+ {/* header block */}
+
+ Resume playback from the saved position, not the start
+
+ {/* section block */}
+
+
+ ❗ P1 · Session replay · PostHog/hogflix
+
+
+ 8.4% of “Continue watching” resumes restart the title from 0:00 —
+ affected sessions run 14% shorter and resume churn is up 3×.
+
+
+ {/* context block */}
+
+ 3 signals · 👤 Suggested reviewers:{" "}
+
+
+ {/* actions block */}
+
+ playCompletionSound("meep")}>
+ Review PR
+
+ playCompletionSound("meep-smol")}>
+ Open in PostHog Code
+
+
+
+
+
+ );
+}
+
+// The answer keeps the first sentence and the value-punch question, collapsing
+// the analysis in between behind an inline "[…]" toggle — enough to tease the
+// depth without dumping a wall of text into the preview.
+const ANSWER_FIRST =
+ "found it — dashboard p75 load time jumped from 0.8s to 3.2s last Tuesday, right when we shipped PostHog/hogflix#4821 (“cache homepage rows per-user”).";
+
+const ANSWER_MIDDLE =
+ "Session replays show the rows skeleton hanging 3–4s on first paint for ~22% of views, and Error tracking has a matching spike in HomeRowsTimeout. The cause is the new per-user cache key — it dropped the shared-row hit rate from 91% to 12%, so nearly every visit recomputes the homepage. I traced it to getHomeRowsCacheKey() in src/server/cache.ts, where #4821 appends the viewer's user id to every key. The fix keeps the shared key and only scopes the user id to the “Because you watched” row; tested against the last week, p75 drops back to ~0.85s.";
+
+const ANSWER_LAST = "Do you want me to ship this fix as a pull request?";
+
+/**
+ * Beat 3 preview: a teammate asks PostHog a one-off in a normal channel, and
+ * PostHog answers — grounded in analytics, error tracking, replay, and the
+ * codebase — then offers to ship the fix. The analysis collapses behind an
+ * inline "[…]" toggle, ending on the value punch: a PR on request.
+ */
+export function SlackAskPostHogPreview() {
+ const [expanded, setExpanded] = useState(false);
+
+ return (
+
+
+ can you look into the dashboard latency
+ complaints? Something regressed in the last week.
+
+
+ {ANSWER_FIRST}{" "}
+ {" "}
+ {expanded && <>{ANSWER_MIDDLE} >}
+ {ANSWER_LAST}
+
+
+ );
+}
diff --git a/packages/ui/src/features/inbox/components/onboarding/InboxOnboardingCallout.tsx b/packages/ui/src/features/inbox/components/onboarding/InboxOnboardingCallout.tsx
new file mode 100644
index 0000000000..c114e55f99
--- /dev/null
+++ b/packages/ui/src/features/inbox/components/onboarding/InboxOnboardingCallout.tsx
@@ -0,0 +1,49 @@
+import { ArrowRightIcon } from "@phosphor-icons/react";
+import {
+ inboxOnboardingProgress,
+ useInboxOnboardingState,
+} from "@posthog/ui/features/inbox/components/onboarding/useInboxOnboardingState";
+import { Flex, Text } from "@radix-ui/themes";
+import { Link } from "@tanstack/react-router";
+
+/**
+ * Slim sticky strip shown above the Agents view's config when the inbox
+ * is still being onboarded. Points back to the Inbox takeover.
+ */
+export function InboxOnboardingCallout() {
+ const state = useInboxOnboardingState();
+ if (state.isLoading || state.isComplete) return null;
+ const progress = inboxOnboardingProgress(state);
+
+ return (
+
+
+
+ Finish setting up your inbox
+
+
+ {progress.doneCount} of {progress.totalCount} done
+
+
+
+ Continue
+
+
+
+
+ );
+}
diff --git a/packages/ui/src/features/inbox/components/onboarding/InboxOnboardingPane.tsx b/packages/ui/src/features/inbox/components/onboarding/InboxOnboardingPane.tsx
new file mode 100644
index 0000000000..11957445f6
--- /dev/null
+++ b/packages/ui/src/features/inbox/components/onboarding/InboxOnboardingPane.tsx
@@ -0,0 +1,414 @@
+import {
+ ArrowLeftIcon,
+ ArrowRightIcon,
+ CheckIcon,
+ SlackLogoIcon,
+ WarningIcon,
+} from "@phosphor-icons/react";
+import { Button, cn } from "@posthog/quill";
+import { formatRelativeTimeLong } from "@posthog/shared";
+import { InboxWelcomeContent } from "@posthog/ui/features/inbox/components/onboarding/InboxOnboardingWelcome";
+import {
+ type InboxOnboardingStep,
+ type InboxOnboardingStepInfo,
+ useInboxOnboardingSessionStore,
+ useInboxOnboardingState,
+} from "@posthog/ui/features/inbox/components/onboarding/useInboxOnboardingState";
+import {
+ type Integration,
+ useIntegrationSelectors,
+} from "@posthog/ui/features/integrations/store";
+import { useIntegrations } from "@posthog/ui/features/integrations/useIntegrations";
+import { useSlackConnect } from "@posthog/ui/features/integrations/useSlackConnect";
+import { GitHubIntegrationSection } from "@posthog/ui/features/settings/sections/GitHubIntegrationSection";
+import { SignalDefaultChannelSettings } from "@posthog/ui/features/settings/sections/SignalDefaultChannelSettings";
+import { SignalSourcesSettings } from "@posthog/ui/features/settings/sections/SignalSourcesSettings";
+import { Flex, Spinner, Text } from "@radix-ui/themes";
+
+const STEP_LABEL: Record = {
+ welcome: "Welcome",
+ github: "GitHub",
+ slack: "Slack",
+ activate: "Agents",
+};
+
+const STEP_META: Record<
+ Exclude,
+ { title: string; subtitle: string }
+> = {
+ github: {
+ title: "Connect GitHub",
+ subtitle:
+ "Point your agents at the code they'll open pull requests against. Connect your org and pick the repo to target by default.",
+ },
+ slack: {
+ title: "Connect Slack",
+ subtitle:
+ "Slack is where your agents deliver reports and take requests. Connect your workspace so everything lands where your team already works.",
+ },
+ activate: {
+ title: "Set up agents",
+ subtitle:
+ "Choose what your agents watch and where reports land. Flip these on and self-driving starts working.",
+ },
+};
+
+/**
+ * Full-screen onboarding takeover shown in place of the inbox tabs until setup
+ * is done. A linear-but-navigable stepper: Welcome → GitHub → Slack → Activate.
+ * The cursor lives in the session store so the user can move backward as well
+ * as forward; Continue is gated on the current step being satisfied.
+ */
+export function InboxOnboardingPane() {
+ const state = useInboxOnboardingState();
+ const goNext = useInboxOnboardingSessionStore((s) => s.goNext);
+ const goBack = useInboxOnboardingSessionStore((s) => s.goBack);
+ const goToStep = useInboxOnboardingSessionStore((s) => s.goToStep);
+ const skipSlack = useInboxOnboardingSessionStore((s) => s.skipSlack);
+ const finish = useInboxOnboardingSessionStore((s) => s.finish);
+ const { slackIntegrations, hasSlackIntegration } = useIntegrationSelectors();
+ const slackIntegrationId = slackIntegrations[0]?.id ?? null;
+
+ if (state.isLoading) return null;
+
+ const { currentStep, currentIndex, currentStepDone, isLastStep, steps } =
+ state;
+ const doneByStep = Object.fromEntries(
+ steps.map((s) => [s.step, s.done]),
+ ) as Record;
+ const isWelcome = currentStep === "welcome";
+ const showSkipSlack = currentStep === "slack" && !hasSlackIntegration;
+
+ const handleSkipSlack = () => {
+ skipSlack();
+ goNext();
+ };
+ const handleContinue = () => {
+ if (isLastStep) finish();
+ else goNext();
+ };
+
+ return (
+