Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
606 changes: 606 additions & 0 deletions apps/desktop/plans/done/20260504-1200-v2-onboarding-flow.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions apps/desktop/src/lib/trpc/routers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { createProjectsRouter } from "./projects";
import { createResourceMetricsRouter } from "./resource-metrics";
import { createRingtoneRouter } from "./ringtone";
import { createSettingsRouter } from "./settings";
import { createSystemRouter } from "./system";
import { createTerminalRouter } from "./terminal";
import { createUiStateRouter } from "./ui-state";
import { createWindowRouter } from "./window";
Expand Down Expand Up @@ -52,6 +53,7 @@ export const createAppRouter = (getWindow: () => BrowserWindow | null) => {
menu: createMenuRouter(),
external: createExternalRouter(),
settings: createSettingsRouter(),
system: createSystemRouter(),
config: createConfigRouter(),
device: createDeviceRouter(),
uiState: createUiStateRouter(),
Expand Down
5 changes: 2 additions & 3 deletions apps/desktop/src/lib/trpc/routers/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,13 @@ export const createPermissionsRouter = () => {

requestAppleEvents: publicProcedure.mutation(async () => {
await shell.openExternal(
"x-apple.systempreferences:com.apple.preference.security?Privacy_Automation",
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Automation",
);
}),

// No deep link exists for Local Network — open the general Privacy & Security pane
requestLocalNetwork: publicProcedure.mutation(async () => {
await shell.openExternal(
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension",
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_LocalNetwork",
);
}),
});
Expand Down
50 changes: 50 additions & 0 deletions apps/desktop/src/lib/trpc/routers/system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { publicProcedure, router } from "..";

const execFileAsync = promisify(execFile);

const KNOWN_GH_PATHS = [
"/opt/homebrew/bin/gh",
"/usr/local/bin/gh",
"/usr/bin/gh",
"/bin/gh",
];

interface GhDetectResult {
installed: boolean;
version: string | null;
path: string | null;
}

async function tryGh(path: string): Promise<GhDetectResult | null> {
try {
const { stdout } = await execFileAsync(path, ["--version"], {
timeout: 3000,
});
const firstLine = stdout.split("\n")[0]?.trim() ?? "";
const match = firstLine.match(/gh version (\S+)/);
const version = match?.[1] ?? null;
return { installed: true, version, path };
} catch {
return null;
}
}

async function detectGhCli(): Promise<GhDetectResult> {
for (const path of KNOWN_GH_PATHS) {
const result = await tryGh(path);
if (result) return result;
}
const result = await tryGh("gh");
if (result) return result;
return { installed: false, version: null, path: null };
}

export const createSystemRouter = () => {
return router({
detectGhCli: publicProcedure.query(detectGhCli),
});
};

export type SystemRouter = ReturnType<typeof createSystemRouter>;
13 changes: 13 additions & 0 deletions apps/desktop/src/renderer/routes/_authenticated/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ import { InitGitDialog } from "renderer/react-query/projects/InitGitDialog";
import { DashboardNewWorkspaceModal } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal";
import { V1MigrationSummaryModal } from "renderer/routes/_authenticated/components/V1MigrationSummaryModal";
import { WorkspaceInitEffects } from "renderer/screens/main/components/WorkspaceInitEffects";
import {
STEP_ROUTES,
selectFirstIncompleteStep,
selectRequiredStepsComplete,
useOnboardingStore,
} from "renderer/stores/onboarding";
import { useSettingsStore } from "renderer/stores/settings-state";
import { useTabsStore } from "renderer/stores/tabs/store";
import { useAgentHookListener } from "renderer/stores/tabs/useAgentHookListener";
Expand Down Expand Up @@ -59,6 +65,8 @@ function AuthenticatedLayout() {
const utils = electronTrpc.useUtils();
const shownWorkspaceInitWarningsRef = useRef(new Set<string>());
const { isV2CloudEnabled } = useIsV2CloudEnabled();
const requiredComplete = useOnboardingStore(selectRequiredStepsComplete);
const firstIncompleteStep = useOnboardingStore(selectFirstIncompleteStep);

const isSignedIn = env.SKIP_ENV_VALIDATION || !!session?.user;
const activeOrganizationId = env.SKIP_ENV_VALIDATION
Expand Down Expand Up @@ -196,6 +204,11 @@ function AuthenticatedLayout() {
return <Navigate to="/create-organization" replace />;
}

const isOnSetupRoute = location.pathname.startsWith("/setup");
if (isV2CloudEnabled && !requiredComplete && !isOnSetupRoute) {
return <Navigate to={STEP_ROUTES[firstIncompleteStep]} replace />;
}

return (
<DndProvider manager={dragDropManager}>
<CollectionsProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@superset/ui/alert-dialog";
import { Button } from "@superset/ui/button";
import { Label } from "@superset/ui/label";
import { toast } from "@superset/ui/sonner";
import { Switch } from "@superset/ui/switch";
import { useNavigate } from "@tanstack/react-router";
import { formatDistanceToNow } from "date-fns";
import { useEffect, useState } from "react";
import { LuRefreshCw } from "react-icons/lu";
Expand All @@ -15,6 +27,7 @@ import {
V1_MIGRATION_LAST_RUN_AT_EVENT,
} from "renderer/routes/_authenticated/hooks/useMigrateV1DataToV2";
import type { MigrationSummary } from "renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate";
import { STEP_ROUTES, useOnboardingStore } from "renderer/stores/onboarding";
import { useV2LocalOverrideStore } from "renderer/stores/v2-local-override";
import { MOCK_ORG_ID } from "shared/constants";
import {
Expand All @@ -38,10 +51,21 @@ export function ExperimentalSettings({
SETTING_ITEM_ID.EXPERIMENTAL_V1_MIGRATION,
visibleItems,
);
const showRestartOnboarding = isItemVisible(
SETTING_ITEM_ID.EXPERIMENTAL_RESTART_ONBOARDING,
visibleItems,
);
const { isV2CloudEnabled, isRemoteV2Enabled } = useIsV2CloudEnabled();
const { rerun, isRunning } = useMigrateV1DataToV2({ autoRun: false });
const setOptInV2 = useV2LocalOverrideStore((state) => state.setOptInV2);
const resetOnboarding = useOnboardingStore((state) => state.reset);
const lastRunAt = useLastMigrationRunAt();
const navigate = useNavigate();

function handleRestartOnboarding() {
resetOnboarding();
void navigate({ to: STEP_ROUTES.providers });
}

async function rerunMigration() {
const result = await rerun();
Expand Down Expand Up @@ -132,6 +156,52 @@ export function ExperimentalSettings({
</Button>
</div>
)}
{showRestartOnboarding && (
<div className="flex items-center justify-between gap-6">
<div className="min-w-0 flex-1 space-y-0.5">
<Label className="text-sm font-medium">Restart onboarding</Label>
<p className="text-xs text-muted-foreground">
Walk through the v2 setup flow again from the beginning.
</p>
{!isV2CloudEnabled && (
<p className="text-xs text-muted-foreground">
Available when v2 is enabled.
</p>
)}
</div>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
type="button"
variant="outline"
size="sm"
disabled={!isV2CloudEnabled}
Comment on lines +172 to +178
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Dialog copy contradicts manualWalkthrough: true behavior

The description promises "Steps you've already satisfied — such as sign-in, providers, and projects — will auto-advance," but reset() sets manualWalkthrough: true, which explicitly disables auto-advance in every step's shouldAutoAdvance guard. After clicking "Restart," users will have to manually click through every step even when providers are already connected and projects already exist — the opposite of what the dialog says.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/settings/experimental/components/ExperimentalSettings/ExperimentalSettings.tsx
Line: 173-179

Comment:
**Dialog copy contradicts `manualWalkthrough: true` behavior**

The description promises "Steps you've already satisfied — such as sign-in, providers, and projects — will auto-advance," but `reset()` sets `manualWalkthrough: true`, which explicitly disables auto-advance in every step's `shouldAutoAdvance` guard. After clicking "Restart," users will have to manually click through every step even when providers are already connected and projects already exist — the opposite of what the dialog says.

How can I resolve this? If you propose a fix, please make it concise.

className="shrink-0"
>
Restart
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Restart onboarding?</AlertDialogTitle>
<AlertDialogDescription>
This clears your onboarding progress and reopens the setup
flow. You'll walk through each step again — for steps you're
already configured for (provider connected, project
attached), you'll see the current status with a Continue
button.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleRestartOnboarding}>
Restart
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const SETTING_ITEM_ID = {

EXPERIMENTAL_SUPERSET_V2: "experimental-superset-v2",
EXPERIMENTAL_V1_MIGRATION: "experimental-v1-migration",
EXPERIMENTAL_RESTART_ONBOARDING: "experimental-restart-onboarding",

INTEGRATIONS_LINEAR: "integrations-linear",
INTEGRATIONS_GITHUB: "integrations-github",
Expand Down Expand Up @@ -152,6 +153,7 @@ export const SETTING_ITEM_VARIANT: Record<SettingItemId, SettingVariant> = {

[SETTING_ITEM_ID.EXPERIMENTAL_SUPERSET_V2]: "shared",
[SETTING_ITEM_ID.EXPERIMENTAL_V1_MIGRATION]: "v2",
[SETTING_ITEM_ID.EXPERIMENTAL_RESTART_ONBOARDING]: "v2",

[SETTING_ITEM_ID.INTEGRATIONS_LINEAR]: "shared",
[SETTING_ITEM_ID.INTEGRATIONS_GITHUB]: "shared",
Expand Down Expand Up @@ -858,6 +860,21 @@ export const SETTINGS_ITEMS: SettingsItem[] = [
"workspaces",
],
},
{
id: SETTING_ITEM_ID.EXPERIMENTAL_RESTART_ONBOARDING,
section: "experimental",
title: "Restart onboarding",
description: "Walk through the v2 setup flow again",
keywords: [
"onboarding",
"setup",
"restart",
"redo",
"walkthrough",
"tour",
"v2",
],
},
{
id: SETTING_ITEM_ID.INTEGRATIONS_LINEAR,
section: "integrations",
Expand Down
Loading
Loading