From 29e2993b0bd548d67a49f9ca32b5a056127e78f5 Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Mon, 30 Mar 2026 12:11:41 +0200
Subject: [PATCH 01/19] Add tests

---
 .../hooks/useGettingStartedItems.ts           |  27 +
 .../home/GettingStartedSection/index.tsx      |   9 +
 .../unit/hooks/useGettingStartedItems.test.ts | 540 ++++++++++++++++++
 .../GettingStartedSectionTest.tsx             | 321 +++++++++++
 4 files changed, 897 insertions(+)
 create mode 100644 src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
 create mode 100644 src/pages/home/GettingStartedSection/index.tsx
 create mode 100644 tests/unit/hooks/useGettingStartedItems.test.ts
 create mode 100644 tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx

diff --git a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
new file mode 100644
index 000000000000..b134d4b4a672
--- /dev/null
+++ b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
@@ -0,0 +1,27 @@
+type GettingStartedItem = {
+    key: string;
+    label: string;
+    isComplete: boolean;
+    route: string;
+};
+
+type UseGettingStartedItemsResult = {
+    shouldShowSection: boolean;
+    items: GettingStartedItem[];
+};
+
+/**
+ * Hook that determines which "Getting Started" checklist items to show
+ * on the Home page for users with the manage-team onboarding intent.
+ *
+ * TODO: Implement logic — this is a stub for TDD.
+ */
+function useGettingStartedItems(): UseGettingStartedItemsResult {
+    return {
+        shouldShowSection: false,
+        items: [],
+    };
+}
+
+export default useGettingStartedItems;
+export type {GettingStartedItem, UseGettingStartedItemsResult};
diff --git a/src/pages/home/GettingStartedSection/index.tsx b/src/pages/home/GettingStartedSection/index.tsx
new file mode 100644
index 000000000000..da29721b4311
--- /dev/null
+++ b/src/pages/home/GettingStartedSection/index.tsx
@@ -0,0 +1,9 @@
+/**
+ * GettingStartedSection — onboarding checklist for manage-team users.
+ * Stub: will be implemented once the hook and completion helpers are ready.
+ */
+function GettingStartedSection() {
+    return null;
+}
+
+export default GettingStartedSection;
diff --git a/tests/unit/hooks/useGettingStartedItems.test.ts b/tests/unit/hooks/useGettingStartedItems.test.ts
new file mode 100644
index 000000000000..44bed0810807
--- /dev/null
+++ b/tests/unit/hooks/useGettingStartedItems.test.ts
@@ -0,0 +1,540 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import {renderHook} from '@testing-library/react-native';
+import Onyx from 'react-native-onyx';
+import useGettingStartedItems from '@pages/home/GettingStartedSection/hooks/useGettingStartedItems';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type {Policy, PolicyCategories} from '@src/types/onyx';
+import createRandomPolicy from '../../utils/collections/policies';
+import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates';
+
+const POLICY_ID = '1';
+
+function buildPolicy(overrides: Partial<Policy> = {}): Policy {
+    return {
+        ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM, 'Test Workspace'),
+        id: POLICY_ID,
+        areCompanyCardsEnabled: false,
+        areRulesEnabled: false,
+        connections: undefined,
+        rules: undefined,
+        customRules: undefined,
+        ...overrides,
+    };
+}
+
+async function setupManageTeamScenario(overrides: {policy?: Partial<Policy>; accounting?: string | null; firstDayTrial?: string; lastDayTrial?: string} = {}) {
+    const policy = buildPolicy(overrides.policy);
+    await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy);
+    await Onyx.merge(ONYXKEYS.NVP_INTRO_SELECTED, {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM});
+    await Onyx.merge(ONYXKEYS.NVP_ACTIVE_POLICY_ID, POLICY_ID);
+
+    if (overrides.accounting !== undefined) {
+        await Onyx.merge(ONYXKEYS.ONBOARDING_USER_REPORTED_INTEGRATION, overrides.accounting as never);
+    }
+
+    const now = new Date();
+    const firstDay = overrides.firstDayTrial ?? new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T').at(0) ?? '';
+    const lastDay = overrides.lastDayTrial ?? new Date(now.getTime() + 23 * 24 * 60 * 60 * 1000).toISOString().split('T').at(0) ?? '';
+    await Onyx.merge(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, firstDay);
+    await Onyx.merge(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, lastDay);
+
+    await waitForBatchedUpdates();
+}
+
+describe('useGettingStartedItems', () => {
+    beforeAll(() => {
+        Onyx.init({keys: ONYXKEYS});
+    });
+
+    beforeEach(async () => {
+        await Onyx.clear();
+        await waitForBatchedUpdates();
+    });
+
+    afterEach(async () => {
+        await Onyx.clear();
+    });
+
+    describe('visibility rules', () => {
+        it('should return no items when onboarding intent is not MANAGE_TEAM', async () => {
+            await Onyx.merge(ONYXKEYS.NVP_INTRO_SELECTED, {choice: CONST.ONBOARDING_CHOICES.PERSONAL_SPEND});
+            await Onyx.merge(ONYXKEYS.NVP_ACTIVE_POLICY_ID, POLICY_ID);
+            const policy = buildPolicy();
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy);
+            await Onyx.merge(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, '2026-03-01');
+            await Onyx.merge(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, '2026-04-01');
+            await waitForBatchedUpdates();
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(false);
+            expect(result.current.items).toEqual([]);
+        });
+
+        it('should return no items when ONBOARDING_PURPOSE_SELECTED is set to a non-manage-team value and NVP_INTRO_SELECTED is not loaded', async () => {
+            await Onyx.merge(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, CONST.ONBOARDING_CHOICES.EMPLOYER);
+            await Onyx.merge(ONYXKEYS.NVP_ACTIVE_POLICY_ID, POLICY_ID);
+            const policy = buildPolicy();
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy);
+            await Onyx.merge(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, '2026-03-01');
+            await Onyx.merge(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, '2026-04-01');
+            await waitForBatchedUpdates();
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(false);
+        });
+
+        it('should use NVP_INTRO_SELECTED.choice as primary source for intent detection', async () => {
+            await setupManageTeamScenario({accounting: CONST.POLICY.CONNECTIONS.NAME.QBO});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(true);
+            expect(result.current.items.length).toBeGreaterThan(0);
+        });
+
+        it('should fall back to ONBOARDING_PURPOSE_SELECTED when NVP_INTRO_SELECTED is not available', async () => {
+            await Onyx.merge(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, CONST.ONBOARDING_CHOICES.MANAGE_TEAM);
+            await Onyx.merge(ONYXKEYS.NVP_ACTIVE_POLICY_ID, POLICY_ID);
+            const policy = buildPolicy();
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy);
+            await Onyx.merge(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, '2026-03-01');
+            await Onyx.merge(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, '2026-04-01');
+            await Onyx.merge(ONYXKEYS.ONBOARDING_USER_REPORTED_INTEGRATION, CONST.POLICY.CONNECTIONS.NAME.QBO as never);
+            await waitForBatchedUpdates();
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(true);
+        });
+
+        it('should be hidden after 60 days from NVP_FIRST_DAY_FREE_TRIAL', async () => {
+            const sixtyOneDaysAgo = new Date(Date.now() - 61 * 24 * 60 * 60 * 1000).toISOString().split('T').at(0) ?? '';
+            const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T').at(0) ?? '';
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                firstDayTrial: sixtyOneDaysAgo,
+                lastDayTrial: thirtyDaysAgo,
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(false);
+        });
+
+        it('should be visible within 60 days from NVP_FIRST_DAY_FREE_TRIAL', async () => {
+            const fiftyNineDaysAgo = new Date(Date.now() - 59 * 24 * 60 * 60 * 1000).toISOString().split('T').at(0) ?? '';
+            const twentyNineDaysAgo = new Date(Date.now() - 29 * 24 * 60 * 60 * 1000).toISOString().split('T').at(0) ?? '';
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                firstDayTrial: fiftyNineDaysAgo,
+                lastDayTrial: twentyNineDaysAgo,
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(true);
+        });
+
+        it('should be hidden when NVP_FIRST_DAY_FREE_TRIAL is not set (not a new sign-up)', async () => {
+            await Onyx.merge(ONYXKEYS.NVP_INTRO_SELECTED, {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM});
+            await Onyx.merge(ONYXKEYS.NVP_ACTIVE_POLICY_ID, POLICY_ID);
+            const policy = buildPolicy();
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy);
+            await Onyx.merge(ONYXKEYS.ONBOARDING_USER_REPORTED_INTEGRATION, CONST.POLICY.CONNECTIONS.NAME.QBO as never);
+            await waitForBatchedUpdates();
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(false);
+        });
+    });
+
+    describe('row 1 - Create a workspace', () => {
+        it('should always be present for manage-team intent', async () => {
+            await setupManageTeamScenario({accounting: CONST.POLICY.CONNECTIONS.NAME.QBO});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const createWorkspaceItem = result.current.items.find((item) => item.key === 'createWorkspace');
+            expect(createWorkspaceItem).toBeDefined();
+        });
+
+        it('should always be checked (completed)', async () => {
+            await setupManageTeamScenario({accounting: CONST.POLICY.CONNECTIONS.NAME.QBO});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const createWorkspaceItem = result.current.items.find((item) => item.key === 'createWorkspace');
+            expect(createWorkspaceItem?.isComplete).toBe(true);
+        });
+
+        it('should be the first item', async () => {
+            await setupManageTeamScenario({accounting: CONST.POLICY.CONNECTIONS.NAME.QBO});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.items.at(0)?.key).toBe('createWorkspace');
+        });
+
+        it('should navigate to the workspace overview route', async () => {
+            await setupManageTeamScenario({accounting: CONST.POLICY.CONNECTIONS.NAME.QBO});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const createWorkspaceItem = result.current.items.find((item) => item.key === 'createWorkspace');
+            expect(createWorkspaceItem?.route).toBe(ROUTES.WORKSPACE_INITIAL.getRoute(POLICY_ID));
+        });
+    });
+
+    describe('row 2a - Connect to [accounting system]', () => {
+        const directConnectIntegrations = [
+            {key: CONST.POLICY.CONNECTIONS.NAME.QBO, name: 'QuickBooks Online'},
+            {key: CONST.POLICY.CONNECTIONS.NAME.QBD, name: 'QuickBooks Desktop'},
+            {key: CONST.POLICY.CONNECTIONS.NAME.XERO, name: 'Xero'},
+            {key: CONST.POLICY.CONNECTIONS.NAME.NETSUITE, name: 'NetSuite'},
+            {key: CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT, name: 'Sage Intacct'},
+        ];
+
+        it.each(directConnectIntegrations)('should show "Connect to $name" when user selected $key in onboarding', async ({key, name}) => {
+            await setupManageTeamScenario({accounting: key});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const connectItem = result.current.items.find((item) => item.key === 'connectAccounting');
+            expect(connectItem).toBeDefined();
+            expect(connectItem?.label).toContain(name);
+        });
+
+        it('should navigate to workspace accounting route', async () => {
+            await setupManageTeamScenario({accounting: CONST.POLICY.CONNECTIONS.NAME.QBO});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const connectItem = result.current.items.find((item) => item.key === 'connectAccounting');
+            expect(connectItem?.route).toBe(ROUTES.WORKSPACE_ACCOUNTING.getRoute(POLICY_ID));
+        });
+
+        it('should be not completed when workspace has no accounting connection', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {connections: undefined},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const connectItem = result.current.items.find((item) => item.key === 'connectAccounting');
+            expect(connectItem?.isComplete).toBe(false);
+        });
+
+        it('should be completed when workspace has a successful accounting connection', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {
+                    connections: {
+                        [CONST.POLICY.CONNECTIONS.NAME.QBO]: {
+                            config: {},
+                            data: {},
+                        },
+                    } as Policy['connections'],
+                },
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const connectItem = result.current.items.find((item) => item.key === 'connectAccounting');
+            expect(connectItem?.isComplete).toBe(true);
+        });
+
+        it('should not show the categories row when showing the connect row', async () => {
+            await setupManageTeamScenario({accounting: CONST.POLICY.CONNECTIONS.NAME.QBO});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const categoriesItem = result.current.items.find((item) => item.key === 'customizeCategories');
+            expect(categoriesItem).toBeUndefined();
+        });
+    });
+
+    describe('row 2b - Customize accounting categories', () => {
+        const categoriesIntegrations = ['sap', 'oracle', 'microsoftDynamics', 'other', 'none', null];
+
+        it.each(categoriesIntegrations)('should show "Customize accounting categories" when accounting choice is %s', async (accounting) => {
+            await setupManageTeamScenario({accounting});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const categoriesItem = result.current.items.find((item) => item.key === 'customizeCategories');
+            expect(categoriesItem).toBeDefined();
+        });
+
+        it('should navigate to workspace categories route', async () => {
+            await setupManageTeamScenario({accounting: 'none'});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const categoriesItem = result.current.items.find((item) => item.key === 'customizeCategories');
+            expect(categoriesItem?.route).toBe(ROUTES.WORKSPACE_CATEGORIES.getRoute(POLICY_ID));
+        });
+
+        it('should not show the connect accounting row when showing the categories row', async () => {
+            await setupManageTeamScenario({accounting: 'none'});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const connectItem = result.current.items.find((item) => item.key === 'connectAccounting');
+            expect(connectItem).toBeUndefined();
+        });
+
+        it('should be not completed when workspace has only default categories', async () => {
+            await setupManageTeamScenario({accounting: 'none'});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const categoriesItem = result.current.items.find((item) => item.key === 'customizeCategories');
+            expect(categoriesItem?.isComplete).toBe(false);
+        });
+
+        it('should be completed when workspace has at least one non-default category', async () => {
+            const customCategories: PolicyCategories = {
+                'Custom Category': {
+                    name: 'Custom Category',
+                    enabled: true,
+                    unencodedName: 'Custom Category',
+                    areCommentsRequired: false,
+                    'GL Code': '',
+                    externalID: '',
+                    origin: '',
+                    previousCategoryName: undefined,
+                },
+            };
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${POLICY_ID}`, customCategories);
+            await setupManageTeamScenario({accounting: 'none'});
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const categoriesItem = result.current.items.find((item) => item.key === 'customizeCategories');
+            expect(categoriesItem?.isComplete).toBe(true);
+        });
+    });
+
+    describe('row 3 - Link company cards', () => {
+        it('should be shown when company cards feature is enabled on the workspace', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {areCompanyCardsEnabled: true},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const companyCardsItem = result.current.items.find((item) => item.key === 'linkCompanyCards');
+            expect(companyCardsItem).toBeDefined();
+        });
+
+        it('should be hidden when company cards feature is not enabled on the workspace', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {areCompanyCardsEnabled: false},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const companyCardsItem = result.current.items.find((item) => item.key === 'linkCompanyCards');
+            expect(companyCardsItem).toBeUndefined();
+        });
+
+        it('should navigate to workspace company cards route', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {areCompanyCardsEnabled: true},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const companyCardsItem = result.current.items.find((item) => item.key === 'linkCompanyCards');
+            expect(companyCardsItem?.route).toBe(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(POLICY_ID));
+        });
+
+        it('should be not completed when no company card feed exists', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {areCompanyCardsEnabled: true},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const companyCardsItem = result.current.items.find((item) => item.key === 'linkCompanyCards');
+            expect(companyCardsItem?.isComplete).toBe(false);
+        });
+    });
+
+    describe('row 4 - Set up spend rules', () => {
+        it('should be shown when rules feature is enabled on the workspace', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {areRulesEnabled: true},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const rulesItem = result.current.items.find((item) => item.key === 'setupRules');
+            expect(rulesItem).toBeDefined();
+        });
+
+        it('should be hidden when rules feature is not enabled on the workspace', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {areRulesEnabled: false},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const rulesItem = result.current.items.find((item) => item.key === 'setupRules');
+            expect(rulesItem).toBeUndefined();
+        });
+
+        it('should navigate to workspace rules route', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {areRulesEnabled: true},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const rulesItem = result.current.items.find((item) => item.key === 'setupRules');
+            expect(rulesItem?.route).toBe(ROUTES.WORKSPACE_RULES.getRoute(POLICY_ID));
+        });
+
+        it('should be not completed when workspace has default rules only', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {areRulesEnabled: true, rules: undefined, customRules: undefined},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const rulesItem = result.current.items.find((item) => item.key === 'setupRules');
+            expect(rulesItem?.isComplete).toBe(false);
+        });
+
+        it('should be completed when workspace has non-default rules', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {
+                    areRulesEnabled: true,
+                    rules: {
+                        approvalRules: [
+                            {
+                                applyWhen: [{condition: 'matches', field: 'amount', value: '1000'}],
+                                approver: 'approver@test.com',
+                                id: 'rule-1',
+                            },
+                        ],
+                    } as Policy['rules'],
+                },
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const rulesItem = result.current.items.find((item) => item.key === 'setupRules');
+            expect(rulesItem?.isComplete).toBe(true);
+        });
+
+        it('should be completed when workspace has custom rules text', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {
+                    areRulesEnabled: true,
+                    customRules: 'All expenses over $500 need manager approval',
+                },
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const rulesItem = result.current.items.find((item) => item.key === 'setupRules');
+            expect(rulesItem?.isComplete).toBe(true);
+        });
+    });
+
+    describe('item ordering', () => {
+        it('should return items in the correct order: createWorkspace, accounting/categories, companyCards, rules', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {areCompanyCardsEnabled: true, areRulesEnabled: true},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const keys = result.current.items.map((item) => item.key);
+            expect(keys).toEqual(['createWorkspace', 'connectAccounting', 'linkCompanyCards', 'setupRules']);
+        });
+
+        it('should return items in the correct order with categories instead of connect', async () => {
+            await setupManageTeamScenario({
+                accounting: 'none',
+                policy: {areCompanyCardsEnabled: true, areRulesEnabled: true},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const keys = result.current.items.map((item) => item.key);
+            expect(keys).toEqual(['createWorkspace', 'customizeCategories', 'linkCompanyCards', 'setupRules']);
+        });
+
+        it('should only contain createWorkspace and accounting row when no optional features are enabled', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {areCompanyCardsEnabled: false, areRulesEnabled: false},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const keys = result.current.items.map((item) => item.key);
+            expect(keys).toEqual(['createWorkspace', 'connectAccounting']);
+        });
+    });
+
+    describe('edge cases', () => {
+        it('should handle missing active policy ID gracefully', async () => {
+            await Onyx.merge(ONYXKEYS.NVP_INTRO_SELECTED, {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM});
+            await Onyx.merge(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, '2026-03-01');
+            await Onyx.merge(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, '2026-04-01');
+            await waitForBatchedUpdates();
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(false);
+        });
+
+        it('should handle missing policy data gracefully', async () => {
+            await Onyx.merge(ONYXKEYS.NVP_INTRO_SELECTED, {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM});
+            await Onyx.merge(ONYXKEYS.NVP_ACTIVE_POLICY_ID, 'nonexistent-policy');
+            await Onyx.merge(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, '2026-03-01');
+            await Onyx.merge(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, '2026-04-01');
+            await waitForBatchedUpdates();
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(false);
+        });
+
+        it('should prefer NVP_INTRO_SELECTED over ONBOARDING_PURPOSE_SELECTED when both are set', async () => {
+            await Onyx.merge(ONYXKEYS.NVP_INTRO_SELECTED, {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM});
+            await Onyx.merge(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, CONST.ONBOARDING_CHOICES.PERSONAL_SPEND);
+            await Onyx.merge(ONYXKEYS.NVP_ACTIVE_POLICY_ID, POLICY_ID);
+            const policy = buildPolicy();
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy);
+            await Onyx.merge(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, '2026-03-01');
+            await Onyx.merge(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, '2026-04-01');
+            await Onyx.merge(ONYXKEYS.ONBOARDING_USER_REPORTED_INTEGRATION, CONST.POLICY.CONNECTIONS.NAME.QBO as never);
+            await waitForBatchedUpdates();
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(true);
+        });
+    });
+});
diff --git a/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx b/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx
new file mode 100644
index 000000000000..5db8e738e164
--- /dev/null
+++ b/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx
@@ -0,0 +1,321 @@
+import {fireEvent, render, screen} from '@testing-library/react-native';
+import Onyx from 'react-native-onyx';
+import Navigation from '@libs/Navigation/Navigation';
+import OnyxListItemProvider from '@src/components/OnyxListItemProvider';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import GettingStartedSection from '@src/pages/home/GettingStartedSection';
+import ROUTES from '@src/ROUTES';
+import waitForBatchedUpdates from '../../../../utils/waitForBatchedUpdates';
+
+const TEST_POLICY_ID = 'ABC123';
+
+jest.mock('@libs/Navigation/Navigation', () => ({
+    navigate: jest.fn(),
+}));
+
+jest.mock('@hooks/useLocalize', () =>
+    jest.fn(() => ({
+        translate: jest.fn((key: string, params?: Record<string, string>) => {
+            if (params && 'integrationName' in params) {
+                return `${key}:${params.integrationName}`;
+            }
+            return key;
+        }),
+    })),
+);
+
+jest.mock('@hooks/useResponsiveLayout', () => jest.fn(() => ({shouldUseNarrowLayout: false})));
+
+const renderGettingStartedSection = () =>
+    render(
+        <OnyxListItemProvider>
+            <GettingStartedSection />
+        </OnyxListItemProvider>,
+    );
+
+/**
+ * Sets up Onyx state for a manage-team user with an active trial
+ * so the section is visible by default.
+ */
+async function setManageTeamUserState(overrides?: {
+    integration?: string | null;
+    areCompanyCardsEnabled?: boolean;
+    areRulesEnabled?: boolean;
+    hasAccountingConnection?: boolean;
+    hasCustomCategories?: boolean;
+    hasCompanyCardConnection?: boolean;
+    hasNonDefaultRules?: boolean;
+    trialStartDate?: string;
+}) {
+    const trialStart = overrides?.trialStartDate ?? '2026-03-01';
+
+    await Onyx.set(ONYXKEYS.NVP_INTRO_SELECTED, {
+        choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM,
+    });
+    await Onyx.set(ONYXKEYS.NVP_ACTIVE_POLICY_ID, TEST_POLICY_ID);
+    await Onyx.set(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, trialStart);
+    await Onyx.set(ONYXKEYS.ONBOARDING_USER_REPORTED_INTEGRATION, (overrides?.integration ?? 'quickbooksOnline') as never);
+
+    const policyData: Record<string, unknown> = {
+        id: TEST_POLICY_ID,
+        name: 'Test Workspace',
+        areCompanyCardsEnabled: overrides?.areCompanyCardsEnabled ?? true,
+        areRulesEnabled: overrides?.areRulesEnabled ?? true,
+    };
+
+    if (overrides?.hasAccountingConnection) {
+        policyData.connections = {
+            quickbooksOnline: {
+                config: {},
+                data: {},
+            },
+        };
+    }
+
+    await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${TEST_POLICY_ID}` as never, policyData as never);
+    await waitForBatchedUpdates();
+}
+
+describe('GettingStartedSection', () => {
+    beforeAll(() => {
+        Onyx.init({keys: ONYXKEYS});
+    });
+
+    beforeEach(async () => {
+        jest.clearAllMocks();
+        await Onyx.clear();
+        await waitForBatchedUpdates();
+    });
+
+    describe('visibility', () => {
+        it('does not render when intent is not manage-team', async () => {
+            await Onyx.set(ONYXKEYS.NVP_INTRO_SELECTED, {
+                choice: CONST.ONBOARDING_CHOICES.PERSONAL_SPEND,
+            });
+            await Onyx.set(ONYXKEYS.NVP_ACTIVE_POLICY_ID, TEST_POLICY_ID);
+            await Onyx.set(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, '2026-03-01');
+            await waitForBatchedUpdates();
+
+            renderGettingStartedSection();
+
+            expect(screen.queryByText('homePage.gettingStartedSection.title')).toBeNull();
+        });
+
+        it('does not render when 60-day cutoff has passed', async () => {
+            await setManageTeamUserState({
+                trialStartDate: '2026-01-01',
+            });
+
+            renderGettingStartedSection();
+
+            expect(screen.queryByText('homePage.gettingStartedSection.title')).toBeNull();
+        });
+
+        it('renders when manage-team intent is set via fallback ONBOARDING_PURPOSE_SELECTED', async () => {
+            await Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, CONST.ONBOARDING_CHOICES.MANAGE_TEAM as never);
+            await Onyx.set(ONYXKEYS.NVP_ACTIVE_POLICY_ID, TEST_POLICY_ID);
+            await Onyx.set(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, '2026-03-01');
+            await Onyx.set(
+                `${ONYXKEYS.COLLECTION.POLICY}${TEST_POLICY_ID}` as never,
+                {
+                    id: TEST_POLICY_ID,
+                    name: 'Test Workspace',
+                    areCompanyCardsEnabled: true,
+                    areRulesEnabled: true,
+                } as never,
+            );
+            await waitForBatchedUpdates();
+
+            renderGettingStartedSection();
+
+            expect(screen.getByText('homePage.gettingStartedSection.title')).toBeTruthy();
+        });
+    });
+
+    describe('section title', () => {
+        it('renders the "Getting started" section title', async () => {
+            await setManageTeamUserState();
+
+            renderGettingStartedSection();
+
+            expect(screen.getByText('homePage.gettingStartedSection.title')).toBeTruthy();
+        });
+    });
+
+    describe('row rendering', () => {
+        it('always shows "Create a workspace" row', async () => {
+            await setManageTeamUserState();
+
+            renderGettingStartedSection();
+
+            expect(screen.getByText('homePage.gettingStartedSection.createWorkspace')).toBeTruthy();
+        });
+
+        it('shows "Connect to [system]" row for QBO integration', async () => {
+            await setManageTeamUserState({integration: 'quickbooksOnline'});
+
+            renderGettingStartedSection();
+
+            expect(screen.getByText(/homePage\.gettingStartedSection\.connectAccounting/)).toBeTruthy();
+        });
+
+        it('shows "Connect to [system]" row for Xero integration', async () => {
+            await setManageTeamUserState({integration: 'xero'});
+
+            renderGettingStartedSection();
+
+            expect(screen.getByText(/homePage\.gettingStartedSection\.connectAccounting/)).toBeTruthy();
+        });
+
+        it('shows "Customize accounting categories" for non-direct-connect integrations', async () => {
+            await setManageTeamUserState({integration: 'other'});
+
+            renderGettingStartedSection();
+
+            expect(screen.getByText('homePage.gettingStartedSection.customizeCategories')).toBeTruthy();
+            expect(screen.queryByText(/homePage\.gettingStartedSection\.connectAccounting/)).toBeNull();
+        });
+
+        it('shows "Customize accounting categories" when no integration is selected', async () => {
+            await setManageTeamUserState({integration: 'none'});
+
+            renderGettingStartedSection();
+
+            expect(screen.getByText('homePage.gettingStartedSection.customizeCategories')).toBeTruthy();
+        });
+
+        it('shows "Link company cards" row when company cards feature is enabled', async () => {
+            await setManageTeamUserState({areCompanyCardsEnabled: true});
+
+            renderGettingStartedSection();
+
+            expect(screen.getByText('homePage.gettingStartedSection.linkCompanyCards')).toBeTruthy();
+        });
+
+        it('hides "Link company cards" row when company cards feature is disabled', async () => {
+            await setManageTeamUserState({areCompanyCardsEnabled: false});
+
+            renderGettingStartedSection();
+
+            expect(screen.queryByText('homePage.gettingStartedSection.linkCompanyCards')).toBeNull();
+        });
+
+        it('shows "Set up spend rules" row when rules feature is enabled', async () => {
+            await setManageTeamUserState({areRulesEnabled: true});
+
+            renderGettingStartedSection();
+
+            expect(screen.getByText('homePage.gettingStartedSection.setupRules')).toBeTruthy();
+        });
+
+        it('hides "Set up spend rules" row when rules feature is disabled', async () => {
+            await setManageTeamUserState({areRulesEnabled: false});
+
+            renderGettingStartedSection();
+
+            expect(screen.queryByText('homePage.gettingStartedSection.setupRules')).toBeNull();
+        });
+
+        it('renders rows in the expected order: workspace, accounting, cards, rules', async () => {
+            await setManageTeamUserState({
+                integration: 'quickbooksOnline',
+                areCompanyCardsEnabled: true,
+                areRulesEnabled: true,
+            });
+
+            renderGettingStartedSection();
+
+            const allRows = screen.getAllByText(/homePage\.gettingStartedSection\./);
+            const rowTexts = allRows.map((el) => (el.props as {children: string}).children);
+            const createIdx = rowTexts.findIndex((t) => t.includes('createWorkspace'));
+            const accountingIdx = rowTexts.findIndex((t) => t.includes('connectAccounting'));
+            const cardsIdx = rowTexts.findIndex((t) => t.includes('linkCompanyCards'));
+            const rulesIdx = rowTexts.findIndex((t) => t.includes('setupRules'));
+
+            expect(createIdx).toBeLessThan(accountingIdx);
+            expect(accountingIdx).toBeLessThan(cardsIdx);
+            expect(cardsIdx).toBeLessThan(rulesIdx);
+        });
+    });
+
+    describe('checked state', () => {
+        it('"Create a workspace" row is always checked', async () => {
+            await setManageTeamUserState();
+
+            renderGettingStartedSection();
+
+            const createRow = screen.getByText('homePage.gettingStartedSection.createWorkspace');
+            const parentRow = createRow.parent;
+            expect(parentRow).toBeTruthy();
+        });
+
+        it('accounting row is checked when workspace has a successful connection', async () => {
+            await setManageTeamUserState({
+                integration: 'quickbooksOnline',
+                hasAccountingConnection: true,
+            });
+
+            renderGettingStartedSection();
+
+            expect(screen.getByText(/homePage\.gettingStartedSection\.connectAccounting/)).toBeTruthy();
+        });
+    });
+
+    describe('navigation', () => {
+        it('navigates to workspace overview when "Create a workspace" row is pressed', async () => {
+            await setManageTeamUserState();
+
+            renderGettingStartedSection();
+
+            const row = screen.getByText('homePage.gettingStartedSection.createWorkspace');
+            fireEvent.press(row);
+
+            expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_INITIAL.getRoute(TEST_POLICY_ID));
+        });
+
+        it('navigates to workspace accounting when "Connect to [system]" row is pressed', async () => {
+            await setManageTeamUserState({integration: 'quickbooksOnline'});
+
+            renderGettingStartedSection();
+
+            const row = screen.getByText(/homePage\.gettingStartedSection\.connectAccounting/);
+            fireEvent.press(row);
+
+            expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_ACCOUNTING.getRoute(TEST_POLICY_ID));
+        });
+
+        it('navigates to workspace categories when "Customize categories" row is pressed', async () => {
+            await setManageTeamUserState({integration: 'other'});
+
+            renderGettingStartedSection();
+
+            const row = screen.getByText('homePage.gettingStartedSection.customizeCategories');
+            fireEvent.press(row);
+
+            expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_CATEGORIES.getRoute(TEST_POLICY_ID));
+        });
+
+        it('navigates to workspace company cards when "Link company cards" row is pressed', async () => {
+            await setManageTeamUserState({areCompanyCardsEnabled: true});
+
+            renderGettingStartedSection();
+
+            const row = screen.getByText('homePage.gettingStartedSection.linkCompanyCards');
+            fireEvent.press(row);
+
+            expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(TEST_POLICY_ID));
+        });
+
+        it('navigates to workspace rules when "Set up spend rules" row is pressed', async () => {
+            await setManageTeamUserState({areRulesEnabled: true});
+
+            renderGettingStartedSection();
+
+            const row = screen.getByText('homePage.gettingStartedSection.setupRules');
+            fireEvent.press(row);
+
+            expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_RULES.getRoute(TEST_POLICY_ID));
+        });
+    });
+});

From 86257b0e5428ec4874daa183a90dd46e83eee184 Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Mon, 30 Mar 2026 17:29:53 +0200
Subject: [PATCH 02/19] Add Getting Started onboarding checklist to Home page

---
 src/languages/en.ts                           |   8 ++
 src/languages/es.ts                           |   8 ++
 src/libs/PolicyUtils.ts                       |  26 +++++
 .../GettingStartedRow.tsx                     |  69 ++++++++++++
 .../hooks/useGettingStartedItems.ts           | 100 ++++++++++++++++--
 .../home/GettingStartedSection/index.tsx      |  35 +++++-
 .../utils/isWithinGettingStartedPeriod.ts     |  18 ++++
 src/pages/home/HomePage.tsx                   |   2 +
 8 files changed, 250 insertions(+), 16 deletions(-)
 create mode 100644 src/pages/home/GettingStartedSection/GettingStartedRow.tsx
 create mode 100644 src/pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod.ts

diff --git a/src/languages/en.ts b/src/languages/en.ts
index 60bc4dd47a11..e41091e5668e 100644
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1093,6 +1093,14 @@ const translations = {
                 fireworksDescription: 'Upcoming to-dos will appear here.',
             },
         },
+        gettingStartedSection: {
+            title: 'Getting started',
+            createWorkspace: 'Create a workspace',
+            connectAccounting: ({integrationName}: {integrationName: string}) => `Connect to ${integrationName}`,
+            customizeCategories: 'Customize accounting categories',
+            linkCompanyCards: 'Link company cards',
+            setupRules: 'Set up spend rules',
+        },
         upcomingTravel: 'Upcoming travel',
         upcomingTravelSection: {
             flightTo: ({destination}: {destination: string}) => `Flight to ${destination}`,
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 5d06b64cda74..f2cf8cd89a46 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -961,6 +961,14 @@ const translations: TranslationDeepObject<typeof en> = {
                 fireworksDescription: 'Las próximas tareas aparecerán aquí.',
             },
         },
+        gettingStartedSection: {
+            title: 'Primeros pasos',
+            createWorkspace: 'Crear un espacio de trabajo',
+            connectAccounting: ({integrationName}: {integrationName: string}) => `Conectar con ${integrationName}`,
+            customizeCategories: 'Personalizar categorías contables',
+            linkCompanyCards: 'Vincular tarjetas corporativas',
+            setupRules: 'Configurar reglas de gasto',
+        },
         upcomingTravel: 'Próximos viajes',
         upcomingTravelSection: {
             flightTo: ({destination}: {destination: string}) => `Vuelo a ${destination}`,
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index e2126652abf4..1e90afcadfd9 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -687,6 +687,31 @@ function hasCustomCategories(policyCategories: OnyxEntry<PolicyCategories>): boo
     return Object.values(policyCategories).some((category) => category && category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && !defaultCategoryNames.has(category.name));
 }
 
+/**
+ * Checks if a policy has any non-default rules configured.
+ * Defaults are: no approval/expense/coding rules and no custom rules text.
+ */
+function hasNonDefaultRules(policy: OnyxEntry<Policy>): boolean {
+    if (!policy) {
+        return false;
+    }
+
+    if (policy.customRules && policy.customRules.trim().length > 0) {
+        return true;
+    }
+
+    const rules = policy.rules;
+    if (!rules) {
+        return false;
+    }
+
+    const hasApprovalRules = !!rules.approvalRules && rules.approvalRules.length > 0;
+    const hasExpenseRules = !!rules.expenseRules && rules.expenseRules.length > 0;
+    const hasCodingRules = !!rules.codingRules && Object.keys(rules.codingRules).length > 0;
+
+    return hasApprovalRules || hasExpenseRules || hasCodingRules;
+}
+
 /**
  * Gets a tag list of a policy by a tag index
  */
@@ -2088,6 +2113,7 @@ export {
     getTagLists,
     hasTags,
     hasCustomCategories,
+    hasNonDefaultRules,
     getTaxByID,
     getUnitRateValue,
     getRateDisplayValue,
diff --git a/src/pages/home/GettingStartedSection/GettingStartedRow.tsx b/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
new file mode 100644
index 000000000000..6d176c9156a1
--- /dev/null
+++ b/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
@@ -0,0 +1,69 @@
+import React, {useCallback} from 'react';
+import {View} from 'react-native';
+import Checkbox from '@components/Checkbox';
+import Icon from '@components/Icon';
+import {PressableWithoutFeedback} from '@components/Pressable';
+import Text from '@components/Text';
+import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
+import variables from '@styles/variables';
+import type {GettingStartedItem} from './hooks/useGettingStartedItems';
+
+type GettingStartedRowProps = {
+    item: GettingStartedItem;
+};
+
+function GettingStartedRow({item}: GettingStartedRowProps) {
+    const styles = useThemeStyles();
+    const theme = useTheme();
+    const StyleUtils = useStyleUtils();
+    const {shouldUseNarrowLayout} = useResponsiveLayout();
+    const icons = useMemoizedLazyExpensifyIcons(['ArrowRight', 'Checkmark'] as const);
+
+    const navigateToItem = useCallback(() => {
+        Navigation.navigate(item.route);
+    }, [item.route]);
+
+    return (
+        <PressableWithoutFeedback
+            onPress={navigateToItem}
+            accessibilityLabel={item.label}
+        >
+            {({hovered}) => (
+                <View style={[styles.flexRow, styles.alignItemsCenter, styles.gap3, shouldUseNarrowLayout ? styles.ph5 : styles.ph8, styles.pv3, hovered && styles.hoveredComponentBG]}>
+                    {item.isComplete ? (
+                        <View style={[StyleUtils.getCheckboxContainerStyle(20, 4), {backgroundColor: theme.icon, borderColor: theme.icon}]}>
+                            <Icon
+                                src={icons.Checkmark}
+                                fill={theme.textLight}
+                                height={14}
+                                width={14}
+                            />
+                        </View>
+                    ) : (
+                        <Checkbox
+                            isChecked={false}
+                            onPress={navigateToItem}
+                            accessibilityLabel={item.label}
+                        />
+                    )}
+                    <Text style={[styles.flex1, styles.textBold, item.isComplete && {color: theme.textSupporting}]}>{item.label}</Text>
+                    {!item.isComplete && (
+                        <Icon
+                            src={icons.ArrowRight}
+                            width={variables.iconSizeNormal}
+                            height={variables.iconSizeNormal}
+                            fill={theme.icon}
+                        />
+                    )}
+                </View>
+            )}
+        </PressableWithoutFeedback>
+    );
+}
+
+export default GettingStartedRow;
diff --git a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
index b134d4b4a672..e1dc058a1a75 100644
--- a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
+++ b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
@@ -1,8 +1,17 @@
+import useLocalize from '@hooks/useLocalize';
+import useOnyx from '@hooks/useOnyx';
+import {hasAccountingConnections, hasCustomCategories, hasNonDefaultRules} from '@libs/PolicyUtils';
+import isWithinGettingStartedPeriod from '@pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type {Route} from '@src/ROUTES';
+
 type GettingStartedItem = {
     key: string;
     label: string;
     isComplete: boolean;
-    route: string;
+    route: Route;
 };
 
 type UseGettingStartedItemsResult = {
@@ -10,17 +19,86 @@ type UseGettingStartedItemsResult = {
     items: GettingStartedItem[];
 };
 
-/**
- * Hook that determines which "Getting Started" checklist items to show
- * on the Home page for users with the manage-team onboarding intent.
- *
- * TODO: Implement logic — this is a stub for TDD.
- */
+const DIRECT_CONNECT_INTEGRATIONS = new Set<string>([
+    CONST.POLICY.CONNECTIONS.NAME.QBO,
+    CONST.POLICY.CONNECTIONS.NAME.QBD,
+    CONST.POLICY.CONNECTIONS.NAME.XERO,
+    CONST.POLICY.CONNECTIONS.NAME.NETSUITE,
+    CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT,
+]);
+
 function useGettingStartedItems(): UseGettingStartedItemsResult {
-    return {
-        shouldShowSection: false,
-        items: [],
-    };
+    const {translate} = useLocalize();
+    const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
+    const [onboardingPurpose] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED);
+    const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
+    const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL);
+    const [reportedIntegration] = useOnyx(ONYXKEYS.ONBOARDING_USER_REPORTED_INTEGRATION);
+    const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID ?? '-1'}`);
+    const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${activePolicyID ?? '-1'}`);
+
+    const emptyResult: UseGettingStartedItemsResult = {shouldShowSection: false, items: []};
+
+    const intent = introSelected?.choice ?? onboardingPurpose;
+    if (intent !== CONST.ONBOARDING_CHOICES.MANAGE_TEAM) {
+        return emptyResult;
+    }
+
+    if (!activePolicyID || !policy) {
+        return emptyResult;
+    }
+
+    if (!isWithinGettingStartedPeriod(firstDayFreeTrial)) {
+        return emptyResult;
+    }
+
+    const items: GettingStartedItem[] = [];
+
+    items.push({
+        key: 'createWorkspace',
+        label: translate('homePage.gettingStartedSection.createWorkspace'),
+        isComplete: true,
+        route: ROUTES.WORKSPACE_OVERVIEW.getRoute(activePolicyID),
+    });
+
+    const isDirectConnect = !!reportedIntegration && DIRECT_CONNECT_INTEGRATIONS.has(reportedIntegration);
+
+    if (isDirectConnect) {
+        const integrationName = CONST.ONBOARDING_ACCOUNTING_MAPPING[reportedIntegration as keyof typeof CONST.ONBOARDING_ACCOUNTING_MAPPING] ?? String(reportedIntegration);
+        items.push({
+            key: 'connectAccounting',
+            label: translate('homePage.gettingStartedSection.connectAccounting', {integrationName}),
+            isComplete: hasAccountingConnections(policy),
+            route: ROUTES.WORKSPACE_ACCOUNTING.getRoute(activePolicyID),
+        });
+    } else {
+        items.push({
+            key: 'customizeCategories',
+            label: translate('homePage.gettingStartedSection.customizeCategories'),
+            isComplete: hasCustomCategories(policyCategories),
+            route: ROUTES.WORKSPACE_CATEGORIES.getRoute(activePolicyID),
+        });
+    }
+
+    if (policy.areCompanyCardsEnabled) {
+        items.push({
+            key: 'linkCompanyCards',
+            label: translate('homePage.gettingStartedSection.linkCompanyCards'),
+            isComplete: false,
+            route: ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(activePolicyID),
+        });
+    }
+
+    if (policy.areRulesEnabled) {
+        items.push({
+            key: 'setupRules',
+            label: translate('homePage.gettingStartedSection.setupRules'),
+            isComplete: hasNonDefaultRules(policy),
+            route: ROUTES.WORKSPACE_RULES.getRoute(activePolicyID),
+        });
+    }
+
+    return {shouldShowSection: true, items};
 }
 
 export default useGettingStartedItems;
diff --git a/src/pages/home/GettingStartedSection/index.tsx b/src/pages/home/GettingStartedSection/index.tsx
index da29721b4311..20ff871f8903 100644
--- a/src/pages/home/GettingStartedSection/index.tsx
+++ b/src/pages/home/GettingStartedSection/index.tsx
@@ -1,9 +1,34 @@
-/**
- * GettingStartedSection — onboarding checklist for manage-team users.
- * Stub: will be implemented once the hook and completion helpers are ready.
- */
+import React from 'react';
+import {View} from 'react-native';
+import WidgetContainer from '@components/WidgetContainer';
+import useLocalize from '@hooks/useLocalize';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useThemeStyles from '@hooks/useThemeStyles';
+import GettingStartedRow from './GettingStartedRow';
+import useGettingStartedItems from './hooks/useGettingStartedItems';
+
 function GettingStartedSection() {
-    return null;
+    const {translate} = useLocalize();
+    const {shouldUseNarrowLayout} = useResponsiveLayout();
+    const styles = useThemeStyles();
+    const {shouldShowSection, items} = useGettingStartedItems();
+
+    if (!shouldShowSection) {
+        return null;
+    }
+
+    return (
+        <WidgetContainer title={translate('homePage.gettingStartedSection.title')}>
+            <View style={styles.getForYouSectionContainerStyle(shouldUseNarrowLayout)}>
+                {items.map((item) => (
+                    <GettingStartedRow
+                        key={item.key}
+                        item={item}
+                    />
+                ))}
+            </View>
+        </WidgetContainer>
+    );
 }
 
 export default GettingStartedSection;
diff --git a/src/pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod.ts b/src/pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod.ts
new file mode 100644
index 000000000000..e9f6ca84b98b
--- /dev/null
+++ b/src/pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod.ts
@@ -0,0 +1,18 @@
+import CONST from '@src/CONST';
+
+const SIXTY_DAYS_MS = 60 * CONST.DATE.SECONDS_PER_DAY * CONST.MILLISECONDS_PER_SECOND;
+
+/**
+ * Checks if the current date is within 60 days of the trial start date.
+ * Returns false if no trial start date is provided.
+ */
+function isWithinGettingStartedPeriod(firstDayFreeTrial: string | undefined): boolean {
+    if (!firstDayFreeTrial) {
+        return false;
+    }
+
+    const trialStartMs = new Date(firstDayFreeTrial).getTime();
+    return Date.now() - trialStartMs <= SIXTY_DAYS_MS;
+}
+
+export default isWithinGettingStartedPeriod;
diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx
index 60ce0badeba2..380e4ebaef39 100644
--- a/src/pages/home/HomePage.tsx
+++ b/src/pages/home/HomePage.tsx
@@ -20,6 +20,7 @@ import AnnouncementSection from './AnnouncementSection';
 import AssignedCardsSection from './AssignedCardsSection';
 import DiscoverSection from './DiscoverSection';
 import ForYouSection from './ForYouSection';
+import GettingStartedSection from './GettingStartedSection';
 import SpendOverTimeSection from './SpendOverTimeSection';
 import TimeSensitiveSection from './TimeSensitiveSection';
 import UpcomingTravelSection from './UpcomingTravelSection';
@@ -81,6 +82,7 @@ function HomePage() {
                                 <DiscoverSection />
                             </View>
                             <View style={styles.homePageRightColumn(shouldUseNarrowLayout)}>
+                                <GettingStartedSection />
                                 <UpcomingTravelSection />
                                 <AssignedCardsSection />
                                 <AnnouncementSection />

From 4a806bd09cf7612ed24e04a1c5e3439dec0c3809 Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Mon, 30 Mar 2026 18:35:42 +0200
Subject: [PATCH 03/19] Simplify control flow in hasNonDefaultRules

---
 src/libs/PolicyUtils.ts | 15 ++++-----------
 1 file changed, 4 insertions(+), 11 deletions(-)

diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index 1e90afcadfd9..2fc2b48ef5fd 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -692,24 +692,17 @@ function hasCustomCategories(policyCategories: OnyxEntry<PolicyCategories>): boo
  * Defaults are: no approval/expense/coding rules and no custom rules text.
  */
 function hasNonDefaultRules(policy: OnyxEntry<Policy>): boolean {
-    if (!policy) {
-        return false;
-    }
-
-    if (policy.customRules && policy.customRules.trim().length > 0) {
-        return true;
-    }
-
-    const rules = policy.rules;
-    if (!rules) {
+    if (!policy?.rules) {
         return false;
     }
 
+    const {rules} = policy;
+    const hasCustomRules = !!policy.customRules && policy.customRules.trim().length > 0;
     const hasApprovalRules = !!rules.approvalRules && rules.approvalRules.length > 0;
     const hasExpenseRules = !!rules.expenseRules && rules.expenseRules.length > 0;
     const hasCodingRules = !!rules.codingRules && Object.keys(rules.codingRules).length > 0;
 
-    return hasApprovalRules || hasExpenseRules || hasCodingRules;
+    return hasCustomRules || hasApprovalRules || hasExpenseRules || hasCodingRules;
 }
 
 /**

From 1875f8b9616c3186db5747d5ebe70d608d7683f5 Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Mon, 30 Mar 2026 18:40:27 +0200
Subject: [PATCH 04/19] Remove unnecessary useCallback from GettingStartedRow

---
 src/pages/home/GettingStartedSection/GettingStartedRow.tsx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/pages/home/GettingStartedSection/GettingStartedRow.tsx b/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
index 6d176c9156a1..e0e96ecf1ee4 100644
--- a/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
+++ b/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback} from 'react';
+import React from 'react';
 import {View} from 'react-native';
 import Checkbox from '@components/Checkbox';
 import Icon from '@components/Icon';
@@ -24,9 +24,9 @@ function GettingStartedRow({item}: GettingStartedRowProps) {
     const {shouldUseNarrowLayout} = useResponsiveLayout();
     const icons = useMemoizedLazyExpensifyIcons(['ArrowRight', 'Checkmark'] as const);
 
-    const navigateToItem = useCallback(() => {
+    const navigateToItem = () => {
         Navigation.navigate(item.route);
-    }, [item.route]);
+    };
 
     return (
         <PressableWithoutFeedback

From 37038417a34d45966e60a2b5e95cbcb4f827a627 Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Mon, 30 Mar 2026 18:48:27 +0200
Subject: [PATCH 05/19] Replace magic numbers with consts in GettingStartedRow

---
 .../home/GettingStartedSection/GettingStartedRow.tsx  | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/pages/home/GettingStartedSection/GettingStartedRow.tsx b/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
index e0e96ecf1ee4..d19628c81c2d 100644
--- a/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
+++ b/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
@@ -36,12 +36,17 @@ function GettingStartedRow({item}: GettingStartedRowProps) {
             {({hovered}) => (
                 <View style={[styles.flexRow, styles.alignItemsCenter, styles.gap3, shouldUseNarrowLayout ? styles.ph5 : styles.ph8, styles.pv3, hovered && styles.hoveredComponentBG]}>
                     {item.isComplete ? (
-                        <View style={[StyleUtils.getCheckboxContainerStyle(20, 4), {backgroundColor: theme.icon, borderColor: theme.icon}]}>
+                        <View
+                            style={[
+                                StyleUtils.getCheckboxContainerStyle(variables.iconSizeNormal, variables.componentBorderRadiusSmall),
+                                {backgroundColor: theme.icon, borderColor: theme.icon},
+                            ]}
+                        >
                             <Icon
                                 src={icons.Checkmark}
                                 fill={theme.textLight}
-                                height={14}
-                                width={14}
+                                height={variables.iconSizeSemiSmall}
+                                width={variables.iconSizeSemiSmall}
                             />
                         </View>
                     ) : (

From 75486208f20ec72291c13b85fcd57093ee80cf7e Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Mon, 30 Mar 2026 18:53:49 +0200
Subject: [PATCH 06/19] Add explanatory comments to eslint-disable directives
 in useGettingStartedItems test

---
 tests/unit/hooks/useGettingStartedItems.test.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tests/unit/hooks/useGettingStartedItems.test.ts b/tests/unit/hooks/useGettingStartedItems.test.ts
index 44bed0810807..5e65e6814e6d 100644
--- a/tests/unit/hooks/useGettingStartedItems.test.ts
+++ b/tests/unit/hooks/useGettingStartedItems.test.ts
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/naming-convention */
 import {renderHook} from '@testing-library/react-native';
 import Onyx from 'react-native-onyx';
 import useGettingStartedItems from '@pages/home/GettingStartedSection/hooks/useGettingStartedItems';
@@ -300,11 +299,13 @@ describe('useGettingStartedItems', () => {
 
         it('should be completed when workspace has at least one non-default category', async () => {
             const customCategories: PolicyCategories = {
+                // eslint-disable-next-line @typescript-eslint/naming-convention -- PolicyCategories keys use human-readable names matching the backend API shape
                 'Custom Category': {
                     name: 'Custom Category',
                     enabled: true,
                     unencodedName: 'Custom Category',
                     areCommentsRequired: false,
+                    // eslint-disable-next-line @typescript-eslint/naming-convention -- matches the backend API field name
                     'GL Code': '',
                     externalID: '',
                     origin: '',

From 7622ea8c1ff6c8a4f83476557ed007e9512b7c7c Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Mon, 30 Mar 2026 19:07:37 +0200
Subject: [PATCH 07/19] Fix isWithinGettingStartedPeriod to reject future trial
 dates

---
 .../utils/isWithinGettingStartedPeriod.ts          |  3 ++-
 tests/unit/hooks/useGettingStartedItems.test.ts    | 14 ++++++++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/src/pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod.ts b/src/pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod.ts
index e9f6ca84b98b..d0ad23cf012b 100644
--- a/src/pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod.ts
+++ b/src/pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod.ts
@@ -12,7 +12,8 @@ function isWithinGettingStartedPeriod(firstDayFreeTrial: string | undefined): bo
     }
 
     const trialStartMs = new Date(firstDayFreeTrial).getTime();
-    return Date.now() - trialStartMs <= SIXTY_DAYS_MS;
+    const elapsed = Date.now() - trialStartMs;
+    return elapsed >= 0 && elapsed <= SIXTY_DAYS_MS;
 }
 
 export default isWithinGettingStartedPeriod;
diff --git a/tests/unit/hooks/useGettingStartedItems.test.ts b/tests/unit/hooks/useGettingStartedItems.test.ts
index 5e65e6814e6d..e0c43422a7ca 100644
--- a/tests/unit/hooks/useGettingStartedItems.test.ts
+++ b/tests/unit/hooks/useGettingStartedItems.test.ts
@@ -138,6 +138,20 @@ describe('useGettingStartedItems', () => {
             expect(result.current.shouldShowSection).toBe(true);
         });
 
+        it('should be hidden when NVP_FIRST_DAY_FREE_TRIAL is in the future (pre-trial)', async () => {
+            const tenDaysFromNow = new Date(Date.now() + 10 * 24 * 60 * 60 * 1000).toISOString().split('T').at(0) ?? '';
+            const fortyDaysFromNow = new Date(Date.now() + 40 * 24 * 60 * 60 * 1000).toISOString().split('T').at(0) ?? '';
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                firstDayTrial: tenDaysFromNow,
+                lastDayTrial: fortyDaysFromNow,
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(false);
+        });
+
         it('should be hidden when NVP_FIRST_DAY_FREE_TRIAL is not set (not a new sign-up)', async () => {
             await Onyx.merge(ONYXKEYS.NVP_INTRO_SELECTED, {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM});
             await Onyx.merge(ONYXKEYS.NVP_ACTIVE_POLICY_ID, POLICY_ID);

From 547cc923f0f87155e45446c51c633129018fdc0f Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Tue, 31 Mar 2026 11:52:27 +0200
Subject: [PATCH 08/19] Fix create-workspace route assertions to use
 WORKSPACE_OVERVIEW

---
 tests/unit/hooks/useGettingStartedItems.test.ts                 | 2 +-
 .../home/GettingStartedSection/GettingStartedSectionTest.tsx    | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/unit/hooks/useGettingStartedItems.test.ts b/tests/unit/hooks/useGettingStartedItems.test.ts
index e0c43422a7ca..c66975115d7b 100644
--- a/tests/unit/hooks/useGettingStartedItems.test.ts
+++ b/tests/unit/hooks/useGettingStartedItems.test.ts
@@ -199,7 +199,7 @@ describe('useGettingStartedItems', () => {
             const {result} = renderHook(() => useGettingStartedItems());
 
             const createWorkspaceItem = result.current.items.find((item) => item.key === 'createWorkspace');
-            expect(createWorkspaceItem?.route).toBe(ROUTES.WORKSPACE_INITIAL.getRoute(POLICY_ID));
+            expect(createWorkspaceItem?.route).toBe(ROUTES.WORKSPACE_OVERVIEW.getRoute(POLICY_ID));
         });
     });
 
diff --git a/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx b/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx
index 5db8e738e164..eb8f11cc9427 100644
--- a/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx
+++ b/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx
@@ -271,7 +271,7 @@ describe('GettingStartedSection', () => {
             const row = screen.getByText('homePage.gettingStartedSection.createWorkspace');
             fireEvent.press(row);
 
-            expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_INITIAL.getRoute(TEST_POLICY_ID));
+            expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_OVERVIEW.getRoute(TEST_POLICY_ID));
         });
 
         it('navigates to workspace accounting when "Connect to [system]" row is pressed', async () => {

From 6bab2b8860faa7a53acf135569c48c3580a1380e Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Tue, 31 Mar 2026 15:54:41 +0200
Subject: [PATCH 09/19] Add gettingStartedSection translations and format
 suggestionsAvailable

---
 src/languages/de.ts      | 17 ++++++++++++++++-
 src/languages/fr.ts      | 17 ++++++++++++++++-
 src/languages/it.ts      | 17 ++++++++++++++++-
 src/languages/ja.ts      | 17 ++++++++++++++++-
 src/languages/nl.ts      | 17 ++++++++++++++++-
 src/languages/pl.ts      | 17 ++++++++++++++++-
 src/languages/pt-BR.ts   | 17 ++++++++++++++++-
 src/languages/zh-hans.ts | 17 ++++++++++++++++-
 8 files changed, 128 insertions(+), 8 deletions(-)

diff --git a/src/languages/de.ts b/src/languages/de.ts
index d1773b887b64..c0487851ccc0 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -1056,6 +1056,14 @@ const translations: TranslationDeepObject<typeof en> = {
             inDays: () => ({one: 'In 1 Tag', other: (count: number) => `In ${count} Tagen`}),
             today: 'Heute',
         },
+        gettingStartedSection: {
+            title: 'Erste Schritte',
+            createWorkspace: 'Workspace erstellen',
+            connectAccounting: ({integrationName}: {integrationName: string}) => `Mit ${integrationName} verbinden`,
+            customizeCategories: 'Buchhaltungskategorien anpassen',
+            linkCompanyCards: 'Firmenkarten verknüpfen',
+            setupRules: 'Ausgabelimits einrichten',
+        },
     },
     allSettingsScreen: {
         subscription: 'Abonnement',
@@ -7442,7 +7450,14 @@ Fordern Sie Spesendetails wie Belege und Beschreibungen an, legen Sie Limits und
         searchIn: 'Suchen in',
         searchPlaceholder: 'Nach etwas suchen',
         suggestions: 'Vorschläge',
-        suggestionsAvailable: ({count}: {count: number}, query = '') => ({
+        suggestionsAvailable: (
+            {
+                count,
+            }: {
+                count: number;
+            },
+            query = '',
+        ) => ({
             one: `Vorschläge verfügbar${query ? ` für ${query}` : ''}. ${count} Ergebnis.`,
             other: (resultCount: number) => `Vorschläge verfügbar${query ? ` für ${query}` : ''}. ${resultCount} Ergebnisse.`,
         }),
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 0038fe534f55..44863588478c 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -1059,6 +1059,14 @@ const translations: TranslationDeepObject<typeof en> = {
             inDays: () => ({one: 'Dans 1 jour', other: (count: number) => `Dans ${count} jours`}),
             today: 'Aujourd’hui',
         },
+        gettingStartedSection: {
+            title: 'Premiers pas',
+            createWorkspace: 'Créer un espace de travail',
+            connectAccounting: ({integrationName}: {integrationName: string}) => `Se connecter à ${integrationName}`,
+            customizeCategories: 'Personnaliser les catégories comptables',
+            linkCompanyCards: 'Lier des cartes d’entreprise',
+            setupRules: 'Configurer les règles de dépense',
+        },
     },
     allSettingsScreen: {
         subscription: 'Abonnement',
@@ -7466,7 +7474,14 @@ Rendez obligatoires des informations de dépense comme les reçus et les descrip
         searchIn: 'Rechercher dans',
         searchPlaceholder: 'Rechercher quelque chose',
         suggestions: 'Suggestions',
-        suggestionsAvailable: ({count}: {count: number}, query = '') => ({
+        suggestionsAvailable: (
+            {
+                count,
+            }: {
+                count: number;
+            },
+            query = '',
+        ) => ({
             one: `Suggestions disponibles${query ? ` pour ${query}` : ''}. ${count} résultat.`,
             other: (resultCount: number) => `Suggestions disponibles${query ? ` pour ${query}` : ''}. ${resultCount} résultats.`,
         }),
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 3cdcd967b43a..66f08e95fd03 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -1056,6 +1056,14 @@ const translations: TranslationDeepObject<typeof en> = {
             inDays: () => ({one: 'Tra 1 giorno', other: (count: number) => `Tra ${count} giorni`}),
             today: 'Oggi',
         },
+        gettingStartedSection: {
+            title: 'Per iniziare',
+            createWorkspace: 'Crea uno spazio di lavoro',
+            connectAccounting: ({integrationName}: {integrationName: string}) => `Connetti a ${integrationName}`,
+            customizeCategories: 'Personalizza le categorie contabili',
+            linkCompanyCards: 'Collega carte aziendali',
+            setupRules: 'Configura le regole di spesa',
+        },
     },
     allSettingsScreen: {
         subscription: 'Abbonamento',
@@ -7430,7 +7438,14 @@ Richiedi dettagli sulle spese come ricevute e descrizioni, imposta limiti e valo
         searchIn: 'Cerca in',
         searchPlaceholder: 'Cerca qualcosa',
         suggestions: 'Suggerimenti',
-        suggestionsAvailable: ({count}: {count: number}, query = '') => ({
+        suggestionsAvailable: (
+            {
+                count,
+            }: {
+                count: number;
+            },
+            query = '',
+        ) => ({
             one: `Suggerimenti disponibili${query ? ` per ${query}` : ''}. ${count} risultato.`,
             other: (resultCount: number) => `Suggerimenti disponibili${query ? ` per ${query}` : ''}. ${resultCount} risultati.`,
         }),
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index d8d6b48c73e2..1fa10b8effa8 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -1039,6 +1039,14 @@ const translations: TranslationDeepObject<typeof en> = {
             inDays: () => ({one: '1日後', other: (count: number) => `${count}日後`}),
             today: '今日',
         },
+        gettingStartedSection: {
+            title: 'はじめに',
+            createWorkspace: 'ワークスペースを作成',
+            connectAccounting: ({integrationName}: {integrationName: string}) => `${integrationName}に接続する`,
+            customizeCategories: '会計カテゴリをカスタマイズする',
+            linkCompanyCards: '会社カードを連携',
+            setupRules: '支出ルールを設定',
+        },
     },
     allSettingsScreen: {
         subscription: 'サブスクリプション',
@@ -7338,7 +7346,14 @@ ${reportName}
         searchIn: '検索対象',
         searchPlaceholder: '何かを検索',
         suggestions: '提案',
-        suggestionsAvailable: ({count}: {count: number}, query = '') => ({
+        suggestionsAvailable: (
+            {
+                count,
+            }: {
+                count: number;
+            },
+            query = '',
+        ) => ({
             one: `候補があります${query ? `: ${query}` : ''}。${count}件の結果。`,
             other: (resultCount: number) => `候補があります${query ? `: ${query}` : ''}。${resultCount}件の結果。`,
         }),
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 6c8670a9375a..cc6170ab73a1 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -1055,6 +1055,14 @@ const translations: TranslationDeepObject<typeof en> = {
             inDays: () => ({one: 'Over 1 dag', other: (count: number) => `Over ${count} dagen`}),
             today: 'Vandaag',
         },
+        gettingStartedSection: {
+            title: 'Aan de slag',
+            createWorkspace: 'Maak een werkruimte',
+            connectAccounting: ({integrationName}: {integrationName: string}) => `Verbind met ${integrationName}`,
+            customizeCategories: 'Boekhoudcategorieën aanpassen',
+            linkCompanyCards: 'Bedrijfspassen koppelen',
+            setupRules: 'Uitgavenregels instellen',
+        },
     },
     allSettingsScreen: {
         subscription: 'Abonnement',
@@ -7396,7 +7404,14 @@ Vereis onkostendetails zoals bonnen en beschrijvingen, stel limieten en standaar
         searchIn: 'Zoeken in',
         searchPlaceholder: 'Zoek iets',
         suggestions: 'Suggesties',
-        suggestionsAvailable: ({count}: {count: number}, query = '') => ({
+        suggestionsAvailable: (
+            {
+                count,
+            }: {
+                count: number;
+            },
+            query = '',
+        ) => ({
             one: `Suggesties beschikbaar${query ? ` voor ${query}` : ''}. ${count} resultaat.`,
             other: (resultCount: number) => `Suggesties beschikbaar${query ? ` voor ${query}` : ''}. ${resultCount} resultaten.`,
         }),
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 1a80b954af5b..d4c17d247c64 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -1056,6 +1056,14 @@ const translations: TranslationDeepObject<typeof en> = {
             inDays: () => ({one: 'Za 1 dzień', other: (count: number) => `Za ${count} dni`}),
             today: 'Dzisiaj',
         },
+        gettingStartedSection: {
+            title: 'Pierwsze kroki',
+            createWorkspace: 'Utwórz przestrzeń roboczą',
+            connectAccounting: ({integrationName}: {integrationName: string}) => `Połącz z ${integrationName}`,
+            customizeCategories: 'Dostosuj kategorie księgowe',
+            linkCompanyCards: 'Połącz firmowe karty',
+            setupRules: 'Skonfiguruj zasady wydatków',
+        },
     },
     allSettingsScreen: {
         subscription: 'Subskrypcja',
@@ -7397,7 +7405,14 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i
         searchIn: 'Szukaj w',
         searchPlaceholder: 'Wyszukaj coś',
         suggestions: 'Sugestie',
-        suggestionsAvailable: ({count}: {count: number}, query = '') => ({
+        suggestionsAvailable: (
+            {
+                count,
+            }: {
+                count: number;
+            },
+            query = '',
+        ) => ({
             one: `Dostępne sugestie${query ? ` dla ${query}` : ''}. ${count} wynik.`,
             other: (resultCount: number) => `Dostępne sugestie${query ? ` dla ${query}` : ''}. ${resultCount} wyniki.`,
         }),
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 6226e66c8f91..81f0b135218b 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -1054,6 +1054,14 @@ const translations: TranslationDeepObject<typeof en> = {
             inDays: () => ({one: 'Em 1 dia', other: (count: number) => `Em ${count} dias`}),
             today: 'Hoje',
         },
+        gettingStartedSection: {
+            title: 'Introdução',
+            createWorkspace: 'Criar um workspace',
+            connectAccounting: ({integrationName}: {integrationName: string}) => `Conectar ao ${integrationName}`,
+            customizeCategories: 'Personalizar categorias contábeis',
+            linkCompanyCards: 'Vincular cartões corporativos',
+            setupRules: 'Configurar regras de gasto',
+        },
     },
     allSettingsScreen: {
         subscription: 'Assinatura',
@@ -7384,7 +7392,14 @@ Exija dados de despesas como recibos e descrições, defina limites e padrões e
         searchIn: 'Pesquisar em',
         searchPlaceholder: 'Pesquisar algo',
         suggestions: 'Sugestões',
-        suggestionsAvailable: ({count}: {count: number}, query = '') => ({
+        suggestionsAvailable: (
+            {
+                count,
+            }: {
+                count: number;
+            },
+            query = '',
+        ) => ({
             one: `Sugestões disponíveis${query ? ` para ${query}` : ''}. ${count} resultado.`,
             other: (resultCount: number) => `Sugestões disponíveis${query ? ` para ${query}` : ''}. ${resultCount} resultados.`,
         }),
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 8ed909dd956a..19dc0e20c5f8 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -1022,6 +1022,14 @@ const translations: TranslationDeepObject<typeof en> = {
             inDays: () => ({one: '1 天后', other: (count: number) => `在 ${count} 天后`}),
             today: '今天',
         },
+        gettingStartedSection: {
+            title: '入门',
+            createWorkspace: '创建工作区',
+            connectAccounting: ({integrationName}: {integrationName: string}) => `连接到 ${integrationName}`,
+            customizeCategories: '自定义会计类别',
+            linkCompanyCards: '关联公司卡',
+            setupRules: '设置消费规则',
+        },
     },
     allSettingsScreen: {
         subscription: '订阅',
@@ -7214,7 +7222,14 @@ ${reportName}
         searchIn: '搜索范围',
         searchPlaceholder: '搜索内容',
         suggestions: '建议',
-        suggestionsAvailable: ({count}: {count: number}, query = '') => ({
+        suggestionsAvailable: (
+            {
+                count,
+            }: {
+                count: number;
+            },
+            query = '',
+        ) => ({
             one: `有可用建议${query ? `：${query}` : ''}。共${count}条结果。`,
             other: (resultCount: number) => `有可用建议${query ? `：${query}` : ''}。共${resultCount}条结果。`,
         }),

From 9405144c2e319570ddb1de636a2004b0d2b567f4 Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Tue, 31 Mar 2026 16:00:26 +0200
Subject: [PATCH 10/19] Add Sentry label to GettingStartedRow pressable

---
 src/CONST/index.ts                                         | 1 +
 src/pages/home/GettingStartedSection/GettingStartedRow.tsx | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index 2bff68a44111..e8ea4d039a25 100644
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -8911,6 +8911,7 @@ const CONST = {
         },
         HOME_PAGE: {
             WIDGET_ITEM: 'HomePage-WidgetItem',
+            GETTING_STARTED_ROW: 'HomePage-GettingStartedRow',
         },
         CALENDAR_PICKER: {
             YEAR_PICKER: 'CalendarPicker-YearPicker',
diff --git a/src/pages/home/GettingStartedSection/GettingStartedRow.tsx b/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
index d19628c81c2d..cd85bc335981 100644
--- a/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
+++ b/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
@@ -11,6 +11,7 @@ import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import Navigation from '@libs/Navigation/Navigation';
 import variables from '@styles/variables';
+import CONST from '@src/CONST';
 import type {GettingStartedItem} from './hooks/useGettingStartedItems';
 
 type GettingStartedRowProps = {
@@ -32,6 +33,7 @@ function GettingStartedRow({item}: GettingStartedRowProps) {
         <PressableWithoutFeedback
             onPress={navigateToItem}
             accessibilityLabel={item.label}
+            sentryLabel={CONST.SENTRY_LABEL.HOME_PAGE.GETTING_STARTED_ROW}
         >
             {({hovered}) => (
                 <View style={[styles.flexRow, styles.alignItemsCenter, styles.gap3, shouldUseNarrowLayout ? styles.ph5 : styles.ph8, styles.pv3, hovered && styles.hoveredComponentBG]}>

From dff7ec9bb70c00ae0d326ebdf5ecc92930ea83e2 Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Tue, 31 Mar 2026 16:03:08 +0200
Subject: [PATCH 11/19] Remove default ID values from useOnyx collection keys

---
 .../GettingStartedSection/hooks/useGettingStartedItems.ts     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
index e1dc058a1a75..6e2e1efa70c8 100644
--- a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
+++ b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
@@ -34,8 +34,8 @@ function useGettingStartedItems(): UseGettingStartedItemsResult {
     const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
     const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL);
     const [reportedIntegration] = useOnyx(ONYXKEYS.ONBOARDING_USER_REPORTED_INTEGRATION);
-    const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID ?? '-1'}`);
-    const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${activePolicyID ?? '-1'}`);
+    const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`);
+    const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${activePolicyID}`);
 
     const emptyResult: UseGettingStartedItemsResult = {shouldShowSection: false, items: []};
 

From d75f4a708082fd38c3a4e5cd600989ff0dc7f263 Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Tue, 31 Mar 2026 16:11:03 +0200
Subject: [PATCH 12/19] Fix hasNonDefaultRules to detect customRules without
 rules object and add useLocalize mock to hook tests

---
 src/libs/PolicyUtils.ts                         | 11 ++++++-----
 tests/unit/hooks/useGettingStartedItems.test.ts | 11 +++++++++++
 2 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index 2fc2b48ef5fd..308d6a9bef56 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -692,15 +692,16 @@ function hasCustomCategories(policyCategories: OnyxEntry<PolicyCategories>): boo
  * Defaults are: no approval/expense/coding rules and no custom rules text.
  */
 function hasNonDefaultRules(policy: OnyxEntry<Policy>): boolean {
-    if (!policy?.rules) {
+    if (!policy) {
         return false;
     }
 
-    const {rules} = policy;
     const hasCustomRules = !!policy.customRules && policy.customRules.trim().length > 0;
-    const hasApprovalRules = !!rules.approvalRules && rules.approvalRules.length > 0;
-    const hasExpenseRules = !!rules.expenseRules && rules.expenseRules.length > 0;
-    const hasCodingRules = !!rules.codingRules && Object.keys(rules.codingRules).length > 0;
+
+    const {rules} = policy;
+    const hasApprovalRules = !!rules?.approvalRules && rules.approvalRules.length > 0;
+    const hasExpenseRules = !!rules?.expenseRules && rules.expenseRules.length > 0;
+    const hasCodingRules = !!rules?.codingRules && Object.keys(rules.codingRules).length > 0;
 
     return hasCustomRules || hasApprovalRules || hasExpenseRules || hasCodingRules;
 }
diff --git a/tests/unit/hooks/useGettingStartedItems.test.ts b/tests/unit/hooks/useGettingStartedItems.test.ts
index c66975115d7b..4e6fc13229f3 100644
--- a/tests/unit/hooks/useGettingStartedItems.test.ts
+++ b/tests/unit/hooks/useGettingStartedItems.test.ts
@@ -8,6 +8,17 @@ import type {Policy, PolicyCategories} from '@src/types/onyx';
 import createRandomPolicy from '../../utils/collections/policies';
 import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates';
 
+jest.mock('@hooks/useLocalize', () =>
+    jest.fn(() => ({
+        translate: jest.fn((key: string, params?: Record<string, string>) => {
+            if (params && 'integrationName' in params) {
+                return `${key}:${params.integrationName}`;
+            }
+            return key;
+        }),
+    })),
+);
+
 const POLICY_ID = '1';
 
 function buildPolicy(overrides: Partial<Policy> = {}): Policy {

From 5297e87b888b4d1367d6af3db26db2c3dafcb95a Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Wed, 1 Apr 2026 17:57:23 +0200
Subject: [PATCH 13/19] Add policy guards and company card feed detection to
 Getting Started section

---
 src/libs/CardUtils.ts                         |  7 +++
 src/libs/actions/OnyxDerived/index.ts         |  3 +-
 .../hooks/useGettingStartedItems.ts           |  9 ++-
 .../unit/hooks/useGettingStartedItems.test.ts | 55 ++++++++++++++++++-
 4 files changed, 67 insertions(+), 7 deletions(-)

diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts
index a7521d1f2287..31f02252b691 100644
--- a/src/libs/CardUtils.ts
+++ b/src/libs/CardUtils.ts
@@ -620,6 +620,12 @@ function getCompanyFeeds(cardFeeds: OnyxEntry<CombinedCardFeeds>, shouldFilterOu
     );
 }
 
+function hasCompanyCardFeeds(cardFeeds: OnyxEntry<CombinedCardFeeds>): boolean {
+    return Object.values(cardFeeds ?? {}).some(
+        (value) => value.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && !value.pending && !value.feed.includes(CONST.EXPENSIFY_CARD.BANK),
+    );
+}
+
 function getBankName(feedType: CardFeedWithNumber | CardFeedWithDomainID): string {
     const cacheKey = feedType ?? '';
     const cached = getBankNameCache.get(cacheKey);
@@ -1590,6 +1596,7 @@ export {
     isSelectedFeedExpired,
     isTravelCard,
     getCompanyFeeds,
+    hasCompanyCardFeeds,
     isPersonalCardBrokenConnection,
     isCustomFeed,
     isCSVFeedOrExpensifyCard,
diff --git a/src/libs/actions/OnyxDerived/index.ts b/src/libs/actions/OnyxDerived/index.ts
index ac245f116b1b..31791077f1ab 100644
--- a/src/libs/actions/OnyxDerived/index.ts
+++ b/src/libs/actions/OnyxDerived/index.ts
@@ -6,7 +6,6 @@
  * The primary purpose is to optimize performance by reducing redundant computations. More info can be found in the README.
  */
 import Onyx from 'react-native-onyx';
-import OnyxKeys from 'react-native-onyx/dist/OnyxKeys';
 import OnyxUtils from 'react-native-onyx/dist/OnyxUtils';
 import Log from '@libs/Log';
 import {endSpan, getSpan, startSpan} from '@libs/telemetry/activeSpans';
@@ -102,7 +101,7 @@ function init() {
                 const dependencyIndex = i;
                 const dependencyOnyxKey = dependencies[dependencyIndex];
 
-                if (OnyxKeys.isCollectionKey(dependencyOnyxKey)) {
+                if (OnyxUtils.isCollectionKey(dependencyOnyxKey)) {
                     Onyx.connectWithoutView({
                         key: dependencyOnyxKey,
                         waitForCollectionCallback: true,
diff --git a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
index 6e2e1efa70c8..e8134b7343ac 100644
--- a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
+++ b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
@@ -1,6 +1,8 @@
+import useCardFeeds from '@hooks/useCardFeeds';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
-import {hasAccountingConnections, hasCustomCategories, hasNonDefaultRules} from '@libs/PolicyUtils';
+import {hasCompanyCardFeeds} from '@libs/CardUtils';
+import {hasAccountingConnections, hasCustomCategories, hasNonDefaultRules, isPaidGroupPolicy, isPendingDeletePolicy} from '@libs/PolicyUtils';
 import isWithinGettingStartedPeriod from '@pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
@@ -36,6 +38,7 @@ function useGettingStartedItems(): UseGettingStartedItemsResult {
     const [reportedIntegration] = useOnyx(ONYXKEYS.ONBOARDING_USER_REPORTED_INTEGRATION);
     const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`);
     const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${activePolicyID}`);
+    const [allCardFeeds] = useCardFeeds(activePolicyID);
 
     const emptyResult: UseGettingStartedItemsResult = {shouldShowSection: false, items: []};
 
@@ -44,7 +47,7 @@ function useGettingStartedItems(): UseGettingStartedItemsResult {
         return emptyResult;
     }
 
-    if (!activePolicyID || !policy) {
+    if (!activePolicyID || !policy || isPendingDeletePolicy(policy) || !isPaidGroupPolicy(policy)) {
         return emptyResult;
     }
 
@@ -84,7 +87,7 @@ function useGettingStartedItems(): UseGettingStartedItemsResult {
         items.push({
             key: 'linkCompanyCards',
             label: translate('homePage.gettingStartedSection.linkCompanyCards'),
-            isComplete: false,
+            isComplete: hasCompanyCardFeeds(allCardFeeds),
             route: ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(activePolicyID),
         });
     }
diff --git a/tests/unit/hooks/useGettingStartedItems.test.ts b/tests/unit/hooks/useGettingStartedItems.test.ts
index 4e6fc13229f3..f8f64281bb60 100644
--- a/tests/unit/hooks/useGettingStartedItems.test.ts
+++ b/tests/unit/hooks/useGettingStartedItems.test.ts
@@ -25,6 +25,7 @@ function buildPolicy(overrides: Partial<Policy> = {}): Policy {
     return {
         ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM, 'Test Workspace'),
         id: POLICY_ID,
+        pendingAction: undefined,
         areCompanyCardsEnabled: false,
         areRulesEnabled: false,
         connections: undefined,
@@ -524,7 +525,7 @@ describe('useGettingStartedItems', () => {
     });
 
     describe('edge cases', () => {
-        it('should handle missing active policy ID gracefully', async () => {
+        it('should be hidden when active policy ID is missing', async () => {
             await Onyx.merge(ONYXKEYS.NVP_INTRO_SELECTED, {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM});
             await Onyx.merge(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, '2026-03-01');
             await Onyx.merge(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, '2026-04-01');
@@ -533,9 +534,22 @@ describe('useGettingStartedItems', () => {
             const {result} = renderHook(() => useGettingStartedItems());
 
             expect(result.current.shouldShowSection).toBe(false);
+            expect(result.current.items).toEqual([]);
+        });
+
+        it('should be hidden when the policy is pending deletion', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(false);
+            expect(result.current.items).toEqual([]);
         });
 
-        it('should handle missing policy data gracefully', async () => {
+        it('should be hidden when policy data does not exist', async () => {
             await Onyx.merge(ONYXKEYS.NVP_INTRO_SELECTED, {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM});
             await Onyx.merge(ONYXKEYS.NVP_ACTIVE_POLICY_ID, 'nonexistent-policy');
             await Onyx.merge(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, '2026-03-01');
@@ -545,6 +559,43 @@ describe('useGettingStartedItems', () => {
             const {result} = renderHook(() => useGettingStartedItems());
 
             expect(result.current.shouldShowSection).toBe(false);
+            expect(result.current.items).toEqual([]);
+        });
+
+        it('should be hidden when active policy is a personal policy', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {type: CONST.POLICY.TYPE.PERSONAL},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(false);
+            expect(result.current.items).toEqual([]);
+        });
+
+        it('should be visible when active policy is a collect (team) policy', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {type: CONST.POLICY.TYPE.TEAM},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(true);
+            expect(result.current.items.length).toBeGreaterThan(0);
+        });
+
+        it('should be visible when active policy is a control (corporate) policy', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {type: CONST.POLICY.TYPE.CORPORATE},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            expect(result.current.shouldShowSection).toBe(true);
+            expect(result.current.items.length).toBeGreaterThan(0);
         });
 
         it('should prefer NVP_INTRO_SELECTED over ONBOARDING_PURPOSE_SELECTED when both are set', async () => {

From 72ec6af8f6e959b911f9efd8154f26bdd2aa290f Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Thu, 2 Apr 2026 11:44:41 +0200
Subject: [PATCH 14/19] Reuse getCompanyFeeds in hasCompanyCardFeeds to remove
 duplicated filtering logic

---
 src/libs/CardUtils.ts | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts
index 31f02252b691..e1d0ae28602f 100644
--- a/src/libs/CardUtils.ts
+++ b/src/libs/CardUtils.ts
@@ -621,9 +621,7 @@ function getCompanyFeeds(cardFeeds: OnyxEntry<CombinedCardFeeds>, shouldFilterOu
 }
 
 function hasCompanyCardFeeds(cardFeeds: OnyxEntry<CombinedCardFeeds>): boolean {
-    return Object.values(cardFeeds ?? {}).some(
-        (value) => value.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && !value.pending && !value.feed.includes(CONST.EXPENSIFY_CARD.BANK),
-    );
+    return Object.keys(getCompanyFeeds(cardFeeds, true, true)).length > 0;
 }
 
 function getBankName(feedType: CardFeedWithNumber | CardFeedWithDomainID): string {

From a886a1f9ea1ad5adf881b7e6bbf76d1bc81987bb Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Thu, 2 Apr 2026 11:49:44 +0200
Subject: [PATCH 15/19] Revert accidental changes

---
 src/libs/actions/OnyxDerived/index.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/libs/actions/OnyxDerived/index.ts b/src/libs/actions/OnyxDerived/index.ts
index 31791077f1ab..ac245f116b1b 100644
--- a/src/libs/actions/OnyxDerived/index.ts
+++ b/src/libs/actions/OnyxDerived/index.ts
@@ -6,6 +6,7 @@
  * The primary purpose is to optimize performance by reducing redundant computations. More info can be found in the README.
  */
 import Onyx from 'react-native-onyx';
+import OnyxKeys from 'react-native-onyx/dist/OnyxKeys';
 import OnyxUtils from 'react-native-onyx/dist/OnyxUtils';
 import Log from '@libs/Log';
 import {endSpan, getSpan, startSpan} from '@libs/telemetry/activeSpans';
@@ -101,7 +102,7 @@ function init() {
                 const dependencyIndex = i;
                 const dependencyOnyxKey = dependencies[dependencyIndex];
 
-                if (OnyxUtils.isCollectionKey(dependencyOnyxKey)) {
+                if (OnyxKeys.isCollectionKey(dependencyOnyxKey)) {
                     Onyx.connectWithoutView({
                         key: dependencyOnyxKey,
                         waitForCollectionCallback: true,

From 4bb453ffa3224ccee6c9a05da90841861e764ec5 Mon Sep 17 00:00:00 2001
From: Adam Grzybowski <adam.grzybowski@swmansion.com>
Date: Thu, 2 Apr 2026 13:45:26 +0200
Subject: [PATCH 16/19] Fix GettingStartedSection tests by adding policy type
 to mock data

---
 .../home/GettingStartedSection/GettingStartedSectionTest.tsx    | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx b/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx
index eb8f11cc9427..dcf2b41c9a6d 100644
--- a/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx
+++ b/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx
@@ -60,6 +60,7 @@ async function setManageTeamUserState(overrides?: {
     const policyData: Record<string, unknown> = {
         id: TEST_POLICY_ID,
         name: 'Test Workspace',
+        type: CONST.POLICY.TYPE.TEAM,
         areCompanyCardsEnabled: overrides?.areCompanyCardsEnabled ?? true,
         areRulesEnabled: overrides?.areRulesEnabled ?? true,
     };
@@ -121,6 +122,7 @@ describe('GettingStartedSection', () => {
                 {
                     id: TEST_POLICY_ID,
                     name: 'Test Workspace',
+                    type: CONST.POLICY.TYPE.TEAM,
                     areCompanyCardsEnabled: true,
                     areRulesEnabled: true,
                 } as never,

From a7c12628300a7a04afb34c68f9732aff17f05350 Mon Sep 17 00:00:00 2001
From: Wojciech Boman <wojciechboman@gmail.com>
Date: Wed, 8 Apr 2026 16:07:45 +0200
Subject: [PATCH 17/19] Navigate to WorkspaceInitialPage on narrow layout in
 GettingStartedSection

---
 .../GettingStartedSection/hooks/useGettingStartedItems.ts     | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
index e8134b7343ac..7288c9236416 100644
--- a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
+++ b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
@@ -1,6 +1,7 @@
 import useCardFeeds from '@hooks/useCardFeeds';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
 import {hasCompanyCardFeeds} from '@libs/CardUtils';
 import {hasAccountingConnections, hasCustomCategories, hasNonDefaultRules, isPaidGroupPolicy, isPendingDeletePolicy} from '@libs/PolicyUtils';
 import isWithinGettingStartedPeriod from '@pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod';
@@ -31,6 +32,7 @@ const DIRECT_CONNECT_INTEGRATIONS = new Set<string>([
 
 function useGettingStartedItems(): UseGettingStartedItemsResult {
     const {translate} = useLocalize();
+    const {shouldUseNarrowLayout} = useResponsiveLayout();
     const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
     const [onboardingPurpose] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED);
     const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
@@ -61,7 +63,7 @@ function useGettingStartedItems(): UseGettingStartedItemsResult {
         key: 'createWorkspace',
         label: translate('homePage.gettingStartedSection.createWorkspace'),
         isComplete: true,
-        route: ROUTES.WORKSPACE_OVERVIEW.getRoute(activePolicyID),
+        route: shouldUseNarrowLayout ? ROUTES.WORKSPACE_INITIAL.getRoute(activePolicyID) : ROUTES.WORKSPACE_OVERVIEW.getRoute(activePolicyID),
     });
 
     const isDirectConnect = !!reportedIntegration && DIRECT_CONNECT_INTEGRATIONS.has(reportedIntegration);

From 11184c8bae5692b99bb199d3f69220dbafb307c9 Mon Sep 17 00:00:00 2001
From: Wojciech Boman <wojciechboman@gmail.com>
Date: Wed, 8 Apr 2026 16:46:04 +0200
Subject: [PATCH 18/19] Enable feature and navigate directly when checklist
 item is disabled

---
 src/libs/actions/Policy/Policy.ts             |  4 +-
 .../GettingStartedRow.tsx                     |  5 ++-
 .../hooks/useGettingStartedItems.ts           | 40 +++++++++++--------
 3 files changed, 30 insertions(+), 19 deletions(-)

diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts
index 06211b30d322..6b1824668421 100644
--- a/src/libs/actions/Policy/Policy.ts
+++ b/src/libs/actions/Policy/Policy.ts
@@ -4391,7 +4391,7 @@ function createWorkspaceFromIOUPayment(
     return {policyID, workspaceChatReportID: memberData.workspaceChatReportID, reportPreviewReportActionID: reportPreviewAction?.reportActionID, adminsChatReportID};
 }
 
-function enablePolicyConnections(policyID: string, enabled: boolean) {
+function enablePolicyConnections(policyID: string, enabled: boolean, shouldGoBack = true) {
     const onyxData: OnyxData<typeof ONYXKEYS.COLLECTION.POLICY> = {
         optimisticData: [
             {
@@ -4434,7 +4434,7 @@ function enablePolicyConnections(policyID: string, enabled: boolean) {
 
     API.writeWithNoDuplicatesEnableFeatureConflicts(WRITE_COMMANDS.ENABLE_POLICY_CONNECTIONS, parameters, onyxData);
 
-    if (enabled && getIsNarrowLayout()) {
+    if (enabled && getIsNarrowLayout() && shouldGoBack) {
         goBackWhenEnableFeature();
     }
 }
diff --git a/src/pages/home/GettingStartedSection/GettingStartedRow.tsx b/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
index cd85bc335981..ea423d828dcb 100644
--- a/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
+++ b/src/pages/home/GettingStartedSection/GettingStartedRow.tsx
@@ -26,7 +26,10 @@ function GettingStartedRow({item}: GettingStartedRowProps) {
     const icons = useMemoizedLazyExpensifyIcons(['ArrowRight', 'Checkmark'] as const);
 
     const navigateToItem = () => {
-        Navigation.navigate(item.route);
+        if (!item.isFeatureEnabled) {
+            item.enableFeature?.();
+        }
+        Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.navigate(item.route));
     };
 
     return (
diff --git a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
index 7288c9236416..826c28ece85b 100644
--- a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
+++ b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
@@ -5,6 +5,8 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
 import {hasCompanyCardFeeds} from '@libs/CardUtils';
 import {hasAccountingConnections, hasCustomCategories, hasNonDefaultRules, isPaidGroupPolicy, isPendingDeletePolicy} from '@libs/PolicyUtils';
 import isWithinGettingStartedPeriod from '@pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod';
+import {enablePolicyCategories} from '@userActions/Policy/Category';
+import {enableCompanyCards, enablePolicyConnections, enablePolicyRules} from '@userActions/Policy/Policy';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
@@ -15,6 +17,8 @@ type GettingStartedItem = {
     label: string;
     isComplete: boolean;
     route: Route;
+    isFeatureEnabled?: boolean;
+    enableFeature?: () => void;
 };
 
 type UseGettingStartedItemsResult = {
@@ -75,6 +79,8 @@ function useGettingStartedItems(): UseGettingStartedItemsResult {
             label: translate('homePage.gettingStartedSection.connectAccounting', {integrationName}),
             isComplete: hasAccountingConnections(policy),
             route: ROUTES.WORKSPACE_ACCOUNTING.getRoute(activePolicyID),
+            isFeatureEnabled: policy.areConnectionsEnabled,
+            enableFeature: () => enablePolicyConnections(activePolicyID, true, false),
         });
     } else {
         items.push({
@@ -82,26 +88,28 @@ function useGettingStartedItems(): UseGettingStartedItemsResult {
             label: translate('homePage.gettingStartedSection.customizeCategories'),
             isComplete: hasCustomCategories(policyCategories),
             route: ROUTES.WORKSPACE_CATEGORIES.getRoute(activePolicyID),
+            isFeatureEnabled: policy.areCategoriesEnabled,
+            enableFeature: () => enablePolicyCategories({policy, categories: policyCategories ?? {}, tags: {}, reports: [], transactionsAndViolations: {}}, true, false),
         });
     }
 
-    if (policy.areCompanyCardsEnabled) {
-        items.push({
-            key: 'linkCompanyCards',
-            label: translate('homePage.gettingStartedSection.linkCompanyCards'),
-            isComplete: hasCompanyCardFeeds(allCardFeeds),
-            route: ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(activePolicyID),
-        });
-    }
+    items.push({
+        key: 'linkCompanyCards',
+        label: translate('homePage.gettingStartedSection.linkCompanyCards'),
+        isComplete: hasCompanyCardFeeds(allCardFeeds),
+        route: ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(activePolicyID),
+        isFeatureEnabled: policy.areCompanyCardsEnabled,
+        enableFeature: () => enableCompanyCards(activePolicyID, true, false),
+    });
 
-    if (policy.areRulesEnabled) {
-        items.push({
-            key: 'setupRules',
-            label: translate('homePage.gettingStartedSection.setupRules'),
-            isComplete: hasNonDefaultRules(policy),
-            route: ROUTES.WORKSPACE_RULES.getRoute(activePolicyID),
-        });
-    }
+    items.push({
+        key: 'setupRules',
+        label: translate('homePage.gettingStartedSection.setupRules'),
+        isComplete: hasNonDefaultRules(policy),
+        route: ROUTES.WORKSPACE_RULES.getRoute(activePolicyID),
+        isFeatureEnabled: policy.areRulesEnabled,
+        enableFeature: () => enablePolicyRules(policy, true, false),
+    });
 
     return {shouldShowSection: true, items};
 }

From 1d8ae1c1fb22be5ea4a88ef8363b9641b7dfc3ed Mon Sep 17 00:00:00 2001
From: Wojciech Boman <wojciechboman@gmail.com>
Date: Thu, 9 Apr 2026 09:09:54 +0200
Subject: [PATCH 19/19] Fix navigating on create workspace button and adjust
 jest tests

---
 .../hooks/useGettingStartedItems.ts           |  3 +-
 .../unit/hooks/useGettingStartedItems.test.ts | 49 +++++++++++++++----
 .../GettingStartedSectionTest.tsx             |  9 ++--
 3 files changed, 46 insertions(+), 15 deletions(-)

diff --git a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
index 826c28ece85b..acdbb8472a77 100644
--- a/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
+++ b/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
@@ -3,6 +3,7 @@ import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
 import useResponsiveLayout from '@hooks/useResponsiveLayout';
 import {hasCompanyCardFeeds} from '@libs/CardUtils';
+import Navigation from '@libs/Navigation/Navigation';
 import {hasAccountingConnections, hasCustomCategories, hasNonDefaultRules, isPaidGroupPolicy, isPendingDeletePolicy} from '@libs/PolicyUtils';
 import isWithinGettingStartedPeriod from '@pages/home/GettingStartedSection/utils/isWithinGettingStartedPeriod';
 import {enablePolicyCategories} from '@userActions/Policy/Category';
@@ -67,7 +68,7 @@ function useGettingStartedItems(): UseGettingStartedItemsResult {
         key: 'createWorkspace',
         label: translate('homePage.gettingStartedSection.createWorkspace'),
         isComplete: true,
-        route: shouldUseNarrowLayout ? ROUTES.WORKSPACE_INITIAL.getRoute(activePolicyID) : ROUTES.WORKSPACE_OVERVIEW.getRoute(activePolicyID),
+        route: shouldUseNarrowLayout ? ROUTES.WORKSPACE_INITIAL.getRoute(activePolicyID, Navigation.getActiveRoute()) : ROUTES.WORKSPACE_OVERVIEW.getRoute(activePolicyID),
     });
 
     const isDirectConnect = !!reportedIntegration && DIRECT_CONNECT_INTEGRATIONS.has(reportedIntegration);
diff --git a/tests/unit/hooks/useGettingStartedItems.test.ts b/tests/unit/hooks/useGettingStartedItems.test.ts
index f8f64281bb60..8b34cfc86598 100644
--- a/tests/unit/hooks/useGettingStartedItems.test.ts
+++ b/tests/unit/hooks/useGettingStartedItems.test.ts
@@ -19,6 +19,11 @@ jest.mock('@hooks/useLocalize', () =>
     })),
 );
 
+jest.mock('@hooks/useResponsiveLayout', () => jest.fn(() => ({shouldUseNarrowLayout: false})));
+
+jest.mock('@userActions/Policy/Category', () => ({enablePolicyCategories: jest.fn()}));
+jest.mock('@userActions/Policy/Policy', () => ({enableCompanyCards: jest.fn(), enablePolicyConnections: jest.fn(), enablePolicyRules: jest.fn()}));
+
 const POLICY_ID = '1';
 
 function buildPolicy(overrides: Partial<Policy> = {}): Policy {
@@ -349,10 +354,10 @@ describe('useGettingStartedItems', () => {
     });
 
     describe('row 3 - Link company cards', () => {
-        it('should be shown when company cards feature is enabled on the workspace', async () => {
+        it('should always be shown', async () => {
             await setupManageTeamScenario({
                 accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
-                policy: {areCompanyCardsEnabled: true},
+                policy: {areCompanyCardsEnabled: false},
             });
 
             const {result} = renderHook(() => useGettingStartedItems());
@@ -361,7 +366,19 @@ describe('useGettingStartedItems', () => {
             expect(companyCardsItem).toBeDefined();
         });
 
-        it('should be hidden when company cards feature is not enabled on the workspace', async () => {
+        it('should have isFeatureEnabled=true when company cards feature is enabled', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {areCompanyCardsEnabled: true},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const companyCardsItem = result.current.items.find((item) => item.key === 'linkCompanyCards');
+            expect(companyCardsItem?.isFeatureEnabled).toBe(true);
+        });
+
+        it('should have isFeatureEnabled=false when company cards feature is not enabled', async () => {
             await setupManageTeamScenario({
                 accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
                 policy: {areCompanyCardsEnabled: false},
@@ -370,7 +387,7 @@ describe('useGettingStartedItems', () => {
             const {result} = renderHook(() => useGettingStartedItems());
 
             const companyCardsItem = result.current.items.find((item) => item.key === 'linkCompanyCards');
-            expect(companyCardsItem).toBeUndefined();
+            expect(companyCardsItem?.isFeatureEnabled).toBe(false);
         });
 
         it('should navigate to workspace company cards route', async () => {
@@ -399,10 +416,10 @@ describe('useGettingStartedItems', () => {
     });
 
     describe('row 4 - Set up spend rules', () => {
-        it('should be shown when rules feature is enabled on the workspace', async () => {
+        it('should always be shown', async () => {
             await setupManageTeamScenario({
                 accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
-                policy: {areRulesEnabled: true},
+                policy: {areRulesEnabled: false},
             });
 
             const {result} = renderHook(() => useGettingStartedItems());
@@ -411,7 +428,19 @@ describe('useGettingStartedItems', () => {
             expect(rulesItem).toBeDefined();
         });
 
-        it('should be hidden when rules feature is not enabled on the workspace', async () => {
+        it('should have isFeatureEnabled=true when rules feature is enabled', async () => {
+            await setupManageTeamScenario({
+                accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
+                policy: {areRulesEnabled: true},
+            });
+
+            const {result} = renderHook(() => useGettingStartedItems());
+
+            const rulesItem = result.current.items.find((item) => item.key === 'setupRules');
+            expect(rulesItem?.isFeatureEnabled).toBe(true);
+        });
+
+        it('should have isFeatureEnabled=false when rules feature is not enabled', async () => {
             await setupManageTeamScenario({
                 accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
                 policy: {areRulesEnabled: false},
@@ -420,7 +449,7 @@ describe('useGettingStartedItems', () => {
             const {result} = renderHook(() => useGettingStartedItems());
 
             const rulesItem = result.current.items.find((item) => item.key === 'setupRules');
-            expect(rulesItem).toBeUndefined();
+            expect(rulesItem?.isFeatureEnabled).toBe(false);
         });
 
         it('should navigate to workspace rules route', async () => {
@@ -511,7 +540,7 @@ describe('useGettingStartedItems', () => {
             expect(keys).toEqual(['createWorkspace', 'customizeCategories', 'linkCompanyCards', 'setupRules']);
         });
 
-        it('should only contain createWorkspace and accounting row when no optional features are enabled', async () => {
+        it('should always contain all four rows even when optional features are disabled', async () => {
             await setupManageTeamScenario({
                 accounting: CONST.POLICY.CONNECTIONS.NAME.QBO,
                 policy: {areCompanyCardsEnabled: false, areRulesEnabled: false},
@@ -520,7 +549,7 @@ describe('useGettingStartedItems', () => {
             const {result} = renderHook(() => useGettingStartedItems());
 
             const keys = result.current.items.map((item) => item.key);
-            expect(keys).toEqual(['createWorkspace', 'connectAccounting']);
+            expect(keys).toEqual(['createWorkspace', 'connectAccounting', 'linkCompanyCards', 'setupRules']);
         });
     });
 
diff --git a/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx b/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx
index dcf2b41c9a6d..a2a8a19a835d 100644
--- a/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx
+++ b/tests/unit/pages/home/GettingStartedSection/GettingStartedSectionTest.tsx
@@ -12,6 +12,7 @@ const TEST_POLICY_ID = 'ABC123';
 
 jest.mock('@libs/Navigation/Navigation', () => ({
     navigate: jest.fn(),
+    setNavigationActionToMicrotaskQueue: jest.fn((callback: () => void) => callback()),
 }));
 
 jest.mock('@hooks/useLocalize', () =>
@@ -195,12 +196,12 @@ describe('GettingStartedSection', () => {
             expect(screen.getByText('homePage.gettingStartedSection.linkCompanyCards')).toBeTruthy();
         });
 
-        it('hides "Link company cards" row when company cards feature is disabled', async () => {
+        it('always shows "Link company cards" row even when company cards feature is disabled', async () => {
             await setManageTeamUserState({areCompanyCardsEnabled: false});
 
             renderGettingStartedSection();
 
-            expect(screen.queryByText('homePage.gettingStartedSection.linkCompanyCards')).toBeNull();
+            expect(screen.getByText('homePage.gettingStartedSection.linkCompanyCards')).toBeTruthy();
         });
 
         it('shows "Set up spend rules" row when rules feature is enabled', async () => {
@@ -211,12 +212,12 @@ describe('GettingStartedSection', () => {
             expect(screen.getByText('homePage.gettingStartedSection.setupRules')).toBeTruthy();
         });
 
-        it('hides "Set up spend rules" row when rules feature is disabled', async () => {
+        it('always shows "Set up spend rules" row even when rules feature is disabled', async () => {
             await setManageTeamUserState({areRulesEnabled: false});
 
             renderGettingStartedSection();
 
-            expect(screen.queryByText('homePage.gettingStartedSection.setupRules')).toBeNull();
+            expect(screen.getByText('homePage.gettingStartedSection.setupRules')).toBeTruthy();
         });
 
         it('renders rows in the expected order: workspace, accounting, cards, rules', async () => {
