From 681f10522ac969cb764ea2ddc89a33dfbf0dd2ea Mon Sep 17 00:00:00 2001
From: Armaan Jain <84474476+Developing-Gamer@users.noreply.github.com>
Date: Wed, 17 Jun 2026 18:14:17 -0700
Subject: [PATCH] Hide sub-apps from dashboard onboarding wizard (#1619)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
Sub-apps (apps with a `parentAppId`, e.g. clickmaps, session-replays,
fraud-protection) were showing up in the project onboarding
app-selection step. They shouldn't — they're automatically enabled when
their parent app is enabled.
```diff
- export const ONBOARDING_APP_IDS = ALL_APP_IDS.filter((appId) => ALL_APPS[appId].stage !== "alpha");
+ export const ONBOARDING_APP_IDS = ALL_APP_IDS.filter((appId) => ALL_APPS[appId].stage !== "alpha" && getParentAppId(appId) == null);
```
Link to Devin session:
https://app.devin.ai/sessions/9f88b9641eea41d0ab6a46f3554fd9cd
Requested by: @Developing-Gamer
---
## Summary by cubic
Hide sub-apps in the project onboarding app selection so only top-level
apps are shown. This prevents users from toggling features that are
auto-enabled by their parent apps.
- **Bug Fixes**
- Filtered `ONBOARDING_APP_IDS` to exclude apps with a parent via
`getParentAppId` (still skips `alpha` apps).
- Added a test to ensure sub-apps never appear in the selection list.
Written for commit 615c0ec9058694a8a1328fd60a6d8930c6cc6a35.
Summary will update on new commits.
## Summary by CodeRabbit
## Release Notes
* **Bug Fixes**
* Fixed onboarding app selection to exclude sub-apps from available
options.
* **Tests**
* Added test coverage for app selection filtering logic.
Co-authored-by: armaan
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---
.../project-onboarding-wizard.test.tsx | 10 +++++++++-
.../new-project/page-client-parts/shared.ts | 4 ++--
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.test.tsx b/apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.test.tsx
index 8e097d760..145853e25 100644
--- a/apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.test.tsx
+++ b/apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.test.tsx
@@ -141,7 +141,7 @@ vi.mock("./link-existing-onboarding", () => ({
import { ProjectOnboardingWizard } from "./project-onboarding-wizard";
import { normalizeProjectOnboardingState, orderedAppIds, REQUIRED_APP_IDS } from "./shared";
-import { ALL_APPS } from "@hexclave/shared/dist/apps/apps-config";
+import { ALL_APPS, getParentAppId, type AppId } from "@hexclave/shared/dist/apps/apps-config";
afterEach(() => {
cleanup();
@@ -171,6 +171,14 @@ describe("ProjectOnboardingWizard", () => {
}
});
+ it("does not offer sub-apps during app selection", () => {
+ const subAppIds = (Object.keys(ALL_APPS) as AppId[]).filter((appId) => getParentAppId(appId) != null);
+
+ for (const subAppId of subAppIds) {
+ expect(orderedAppIds()).not.toContain(subAppId);
+ }
+ });
+
it("completes onboarding automatically after Stripe setup returns successfully", async () => {
const setStatus = vi.fn(async () => {});
const onComplete = vi.fn();
diff --git a/apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/shared.ts b/apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/shared.ts
index e1afc8e4e..3e1fdd22e 100644
--- a/apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/shared.ts
+++ b/apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/shared.ts
@@ -1,6 +1,6 @@
import { hexclaveAppInternalsSymbol } from "@/lib/hexclave-app-internals";
import { AdminOwnedProject } from "@hexclave/next";
-import { ALL_APPS, type AppId } from "@hexclave/shared/dist/apps/apps-config";
+import { ALL_APPS, getParentAppId, type AppId } from "@hexclave/shared/dist/apps/apps-config";
import { projectOnboardingStatusValues, type ProjectOnboardingStatus } from "@hexclave/shared/dist/schema-fields";
import { sharedProviders } from "@hexclave/shared/dist/utils/oauth";
import { stringCompare } from "@hexclave/shared/dist/utils/strings";
@@ -23,7 +23,7 @@ export const SIGN_IN_METHODS: Array<{ id: SignInMethod, label: string }> = [
export const REQUIRED_APP_IDS: AppId[] = ["authentication", "emails"];
export const PRIMARY_APP_IDS: AppId[] = ["authentication", "emails", "payments", "analytics"];
export const ALL_APP_IDS = Object.keys(ALL_APPS) as AppId[];
-export const ONBOARDING_APP_IDS = ALL_APP_IDS.filter((appId) => ALL_APPS[appId].stage !== "alpha");
+export const ONBOARDING_APP_IDS = ALL_APP_IDS.filter((appId) => ALL_APPS[appId].stage !== "alpha" && getParentAppId(appId) == null);
export const OAUTH_SIGN_IN_METHODS = ["google", "github", "microsoft"] satisfies SignInMethod[];
export const SHARED_OAUTH_SIGN_IN_METHODS = sharedProviders.filter((provider): provider is (typeof sharedProviders)[number] & SignInMethod => {
return OAUTH_SIGN_IN_METHODS.some((method) => method === provider);