diff --git a/apps/mobile/src/app/(app)/_layout.tsx b/apps/mobile/src/app/(app)/_layout.tsx index 52ea0e82740..33d0e9ef843 100644 --- a/apps/mobile/src/app/(app)/_layout.tsx +++ b/apps/mobile/src/app/(app)/_layout.tsx @@ -30,6 +30,7 @@ import { useSecurityLifecycleInvalidation } from '@/lib/hooks/use-security-lifec import { CachePersistenceMount } from '@/lib/persist/cache-persistence-mount'; import { SystemSearchIndexMount } from '@/lib/system-search-index-mount'; import { ToolSummaryTranslationRetryMount } from '@/lib/tool-summary-translation/tool-summary-translation-retry-mount'; +import { TabBarLabelContext } from '@/lib/tab-bar-clearance'; import { useTRPC } from '@/lib/trpc'; /** @@ -134,97 +135,101 @@ export default function AppLayout() { - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/_layout.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/_layout.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/_layout.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/_layout.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/focus-areas.mounted.test.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/focus-areas.mounted.test.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/focus-areas.mounted.test.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/focus-areas.mounted.test.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/focus-areas.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/focus-areas.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/focus-areas.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/focus-areas.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/gate.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/gate.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/gate.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/gate.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/instructions.mounted.test.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/instructions.mounted.test.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/instructions.mounted.test.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/instructions.mounted.test.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/instructions.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/instructions.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/instructions.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/instructions.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/repos.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/repos.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/repos.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/repos.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/style.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/style.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/style.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/(edit)/style.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/_layout.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/_layout.tsx similarity index 87% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/_layout.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/_layout.tsx index 88b793d5cb0..b24e8929bad 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/_layout.tsx +++ b/apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/_layout.tsx @@ -11,7 +11,7 @@ export default function CodeReviewerPlatformLayout() { const params = useValidatedReviewerRouteParams(); if (!params) { - return ; + return ; } return ; diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/index.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/index.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/index.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/[platform]/index.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/index.mounted.test.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/index.mounted.test.tsx similarity index 89% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/index.mounted.test.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/index.mounted.test.tsx index ebb461197ec..efc078bfbba 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/index.mounted.test.tsx +++ b/apps/mobile/src/app/(app)/code-reviewer/[scope]/index.mounted.test.tsx @@ -50,13 +50,13 @@ beforeEach(() => { }); describe('CodeReviewerScopeRoute invalid scope', () => { - it('renders InvalidRouteState with the profile backTo when scope is undefined', () => { + it('renders InvalidRouteState with the Code Reviewer backTo when scope is undefined', () => { useLocalSearchParamsMock.mockReturnValue({ scope: undefined }); const renderer = mountRoute(); const invalid = findByType(renderer.root, 'InvalidRouteState'); expect(invalid).toHaveLength(1); - expect(propOf(invalid[0], 'backTo')).toBe('/(app)/(tabs)/(3_profile)'); + expect(propOf(invalid[0], 'backTo')).toBe('/(app)/code-reviewer'); expect(findByType(renderer.root, 'PlatformListScreen')).toHaveLength(0); act(() => { @@ -64,13 +64,13 @@ describe('CodeReviewerScopeRoute invalid scope', () => { }); }); - it('renders InvalidRouteState with the profile backTo when scope is an array', () => { + it('renders InvalidRouteState with the Code Reviewer backTo when scope is an array', () => { useLocalSearchParamsMock.mockReturnValue({ scope: ['personal', 'org'] }); const renderer = mountRoute(); const invalid = findByType(renderer.root, 'InvalidRouteState'); expect(invalid).toHaveLength(1); - expect(propOf(invalid[0], 'backTo')).toBe('/(app)/(tabs)/(3_profile)'); + expect(propOf(invalid[0], 'backTo')).toBe('/(app)/code-reviewer'); expect(findByType(renderer.root, 'PlatformListScreen')).toHaveLength(0); act(() => { diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/index.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/index.tsx similarity index 86% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/index.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/index.tsx index 7d5feaf312e..0584c7e7365 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/index.tsx +++ b/apps/mobile/src/app/(app)/code-reviewer/[scope]/index.tsx @@ -9,7 +9,7 @@ export default function CodeReviewerScopeRoute() { const scope = parseParam(rawScope); if (!scope) { - return ; + return ; } return ; diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/manual-review.mounted.test.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/manual-review.mounted.test.tsx similarity index 89% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/manual-review.mounted.test.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/manual-review.mounted.test.tsx index ea0d669103b..fa14e241056 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/manual-review.mounted.test.tsx +++ b/apps/mobile/src/app/(app)/code-reviewer/[scope]/manual-review.mounted.test.tsx @@ -50,13 +50,13 @@ beforeEach(() => { }); describe('CodeReviewerManualReviewRoute invalid scope', () => { - it('renders InvalidRouteState with the profile backTo when scope is undefined', () => { + it('renders InvalidRouteState with the Code Reviewer backTo when scope is undefined', () => { useLocalSearchParamsMock.mockReturnValue({ scope: undefined }); const renderer = mountRoute(); const invalid = findByType(renderer.root, 'InvalidRouteState'); expect(invalid).toHaveLength(1); - expect(propOf(invalid[0], 'backTo')).toBe('/(app)/(tabs)/(3_profile)'); + expect(propOf(invalid[0], 'backTo')).toBe('/(app)/code-reviewer'); expect(findByType(renderer.root, 'ManualReviewScreen')).toHaveLength(0); act(() => { @@ -64,13 +64,13 @@ describe('CodeReviewerManualReviewRoute invalid scope', () => { }); }); - it('renders InvalidRouteState with the profile backTo when scope is an array', () => { + it('renders InvalidRouteState with the Code Reviewer backTo when scope is an array', () => { useLocalSearchParamsMock.mockReturnValue({ scope: ['personal', 'org'] }); const renderer = mountRoute(); const invalid = findByType(renderer.root, 'InvalidRouteState'); expect(invalid).toHaveLength(1); - expect(propOf(invalid[0], 'backTo')).toBe('/(app)/(tabs)/(3_profile)'); + expect(propOf(invalid[0], 'backTo')).toBe('/(app)/code-reviewer'); expect(findByType(renderer.root, 'ManualReviewScreen')).toHaveLength(0); act(() => { diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/manual-review.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/manual-review.tsx similarity index 86% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/manual-review.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/manual-review.tsx index 17786d4d302..95a78734be1 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/manual-review.tsx +++ b/apps/mobile/src/app/(app)/code-reviewer/[scope]/manual-review.tsx @@ -9,7 +9,7 @@ export default function CodeReviewerManualReviewRoute() { const scope = parseParam(rawScope); if (!scope) { - return ; + return ; } return ; diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/review-memory.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/review-memory.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/review-memory.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/review-memory.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/[id].mounted.test.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/reviews/[id].mounted.test.tsx similarity index 87% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/[id].mounted.test.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/reviews/[id].mounted.test.tsx index 96b5e994d95..34fbe70a258 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/[id].mounted.test.tsx +++ b/apps/mobile/src/app/(app)/code-reviewer/[scope]/reviews/[id].mounted.test.tsx @@ -50,13 +50,13 @@ beforeEach(() => { }); describe('CodeReviewerReviewDetailRoute invalid scope', () => { - it('renders InvalidRouteState with the profile backTo when scope is undefined', () => { + it('renders InvalidRouteState with the Code Reviewer backTo when scope is undefined', () => { useLocalSearchParamsMock.mockReturnValue({ scope: undefined, id: 'rev-1' }); const renderer = mountRoute(); const invalid = findByType(renderer.root, 'InvalidRouteState'); expect(invalid).toHaveLength(1); - expect(propOf(invalid[0], 'backTo')).toBe('/(app)/(tabs)/(3_profile)'); + expect(propOf(invalid[0], 'backTo')).toBe('/(app)/code-reviewer'); expect(findByType(renderer.root, 'ReviewDetailScreen')).toHaveLength(0); act(() => { @@ -64,13 +64,13 @@ describe('CodeReviewerReviewDetailRoute invalid scope', () => { }); }); - it('renders InvalidRouteState with the profile backTo when scope is an array', () => { + it('renders InvalidRouteState with the Code Reviewer backTo when scope is an array', () => { useLocalSearchParamsMock.mockReturnValue({ scope: ['personal', 'org'], id: 'rev-1' }); const renderer = mountRoute(); const invalid = findByType(renderer.root, 'InvalidRouteState'); expect(invalid).toHaveLength(1); - expect(propOf(invalid[0], 'backTo')).toBe('/(app)/(tabs)/(3_profile)'); + expect(propOf(invalid[0], 'backTo')).toBe('/(app)/code-reviewer'); expect(findByType(renderer.root, 'ReviewDetailScreen')).toHaveLength(0); act(() => { @@ -86,9 +86,7 @@ describe('CodeReviewerReviewDetailRoute invalid id', () => { const invalid = findByType(renderer.root, 'InvalidRouteState'); expect(invalid).toHaveLength(1); - expect(propOf(invalid[0], 'backTo')).toBe( - '/(app)/(tabs)/(3_profile)/code-reviewer/personal/reviews' - ); + expect(propOf(invalid[0], 'backTo')).toBe('/(app)/code-reviewer/personal/reviews'); expect(findByType(renderer.root, 'ReviewDetailScreen')).toHaveLength(0); act(() => { @@ -102,9 +100,7 @@ describe('CodeReviewerReviewDetailRoute invalid id', () => { const invalid = findByType(renderer.root, 'InvalidRouteState'); expect(invalid).toHaveLength(1); - expect(propOf(invalid[0], 'backTo')).toBe( - '/(app)/(tabs)/(3_profile)/code-reviewer/personal/reviews' - ); + expect(propOf(invalid[0], 'backTo')).toBe('/(app)/code-reviewer/personal/reviews'); expect(findByType(renderer.root, 'ReviewDetailScreen')).toHaveLength(0); act(() => { diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/[id].tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/reviews/[id].tsx similarity index 85% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/[id].tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/reviews/[id].tsx index 104d78a912c..a459115c49b 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/[id].tsx +++ b/apps/mobile/src/app/(app)/code-reviewer/[scope]/reviews/[id].tsx @@ -11,9 +11,7 @@ export default function CodeReviewerReviewDetailRoute() { if (!scope || !reviewId) { const backTo = ( - scope - ? `/(app)/(tabs)/(3_profile)/code-reviewer/${scope}/reviews` - : '/(app)/(tabs)/(3_profile)' + scope ? `/(app)/code-reviewer/${scope}/reviews` : '/(app)/code-reviewer' ) as Href; return ; } diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/index.mounted.test.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/reviews/index.mounted.test.tsx similarity index 89% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/index.mounted.test.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/reviews/index.mounted.test.tsx index 67a7f34abdd..20b86da9fd6 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/index.mounted.test.tsx +++ b/apps/mobile/src/app/(app)/code-reviewer/[scope]/reviews/index.mounted.test.tsx @@ -50,13 +50,13 @@ beforeEach(() => { }); describe('CodeReviewerReviewListRoute invalid scope', () => { - it('renders InvalidRouteState with the profile backTo when scope is undefined', () => { + it('renders InvalidRouteState with the Code Reviewer backTo when scope is undefined', () => { useLocalSearchParamsMock.mockReturnValue({ scope: undefined }); const renderer = mountRoute(); const invalid = findByType(renderer.root, 'InvalidRouteState'); expect(invalid).toHaveLength(1); - expect(propOf(invalid[0], 'backTo')).toBe('/(app)/(tabs)/(3_profile)'); + expect(propOf(invalid[0], 'backTo')).toBe('/(app)/code-reviewer'); expect(findByType(renderer.root, 'ReviewListScreen')).toHaveLength(0); act(() => { @@ -64,13 +64,13 @@ describe('CodeReviewerReviewListRoute invalid scope', () => { }); }); - it('renders InvalidRouteState with the profile backTo when scope is an array', () => { + it('renders InvalidRouteState with the Code Reviewer backTo when scope is an array', () => { useLocalSearchParamsMock.mockReturnValue({ scope: ['personal', 'org'] }); const renderer = mountRoute(); const invalid = findByType(renderer.root, 'InvalidRouteState'); expect(invalid).toHaveLength(1); - expect(propOf(invalid[0], 'backTo')).toBe('/(app)/(tabs)/(3_profile)'); + expect(propOf(invalid[0], 'backTo')).toBe('/(app)/code-reviewer'); expect(findByType(renderer.root, 'ReviewListScreen')).toHaveLength(0); act(() => { diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/index.tsx b/apps/mobile/src/app/(app)/code-reviewer/[scope]/reviews/index.tsx similarity index 86% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/index.tsx rename to apps/mobile/src/app/(app)/code-reviewer/[scope]/reviews/index.tsx index 75e3ac3eb2b..89bb3905779 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/index.tsx +++ b/apps/mobile/src/app/(app)/code-reviewer/[scope]/reviews/index.tsx @@ -9,7 +9,7 @@ export default function CodeReviewerReviewListRoute() { const scope = parseParam(rawScope); if (!scope) { - return ; + return ; } return ; diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/index.tsx b/apps/mobile/src/app/(app)/code-reviewer/index.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/index.tsx rename to apps/mobile/src/app/(app)/code-reviewer/index.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/_layout.tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/_layout.tsx similarity index 94% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/_layout.tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/_layout.tsx index 0ce0fb9a1b7..cbae3a064ea 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/_layout.tsx +++ b/apps/mobile/src/app/(app)/security-agent/[scope]/_layout.tsx @@ -22,7 +22,7 @@ export default function SecurityAgentScopeLayout() { const sheetOptions = useFormSheetScreenOptions(); if (!scope) { - return ; + return ; } return ( diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/audit-report.tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/audit-report.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/audit-report.tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/audit-report.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/dismiss/[id].tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/dismiss/[id].tsx similarity index 82% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/dismiss/[id].tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/dismiss/[id].tsx index 2a0c2436302..a475700d253 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/dismiss/[id].tsx +++ b/apps/mobile/src/app/(app)/security-agent/[scope]/dismiss/[id].tsx @@ -9,9 +9,7 @@ export default function SecurityAgentDismissFindingRoute() { const findingId = parseParam(rawId); if (!findingId) { - return ( - - ); + return ; } return ; diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/filter.tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/filter.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/filter.tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/filter.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/findings/[id].tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/findings/[id].tsx similarity index 82% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/findings/[id].tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/findings/[id].tsx index f47d2e08e29..57f70f97460 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/findings/[id].tsx +++ b/apps/mobile/src/app/(app)/security-agent/[scope]/findings/[id].tsx @@ -9,9 +9,7 @@ export default function SecurityAgentFindingDetailRoute() { const findingId = parseParam(rawId); if (!findingId) { - return ( - - ); + return ; } return ; diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/findings/index.tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/findings/index.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/findings/index.tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/findings/index.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/index.tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/index.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/index.tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/index.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/settings/analysis.tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/settings/analysis.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/settings/analysis.tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/settings/analysis.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/settings/automation.tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/settings/automation.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/settings/automation.tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/settings/automation.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/settings/index.tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/settings/index.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/settings/index.tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/settings/index.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/settings/notifications.tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/settings/notifications.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/settings/notifications.tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/settings/notifications.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/settings/repositories.tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/settings/repositories.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/settings/repositories.tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/settings/repositories.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/settings/sla.tsx b/apps/mobile/src/app/(app)/security-agent/[scope]/settings/sla.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/settings/sla.tsx rename to apps/mobile/src/app/(app)/security-agent/[scope]/settings/sla.tsx diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/index.tsx b/apps/mobile/src/app/(app)/security-agent/index.tsx similarity index 100% rename from apps/mobile/src/app/(app)/(tabs)/(3_profile)/security-agent/index.tsx rename to apps/mobile/src/app/(app)/security-agent/index.tsx diff --git a/apps/mobile/src/components/app-unlock-screen.mounted.test.tsx b/apps/mobile/src/components/app-unlock-screen.mounted.test.tsx index c9c1c2c0ccc..a383cbdf2a8 100644 --- a/apps/mobile/src/components/app-unlock-screen.mounted.test.tsx +++ b/apps/mobile/src/components/app-unlock-screen.mounted.test.tsx @@ -26,7 +26,7 @@ import fr from '@/i18n/locales/fr.json'; import AppLayout from '@/app/(app)/_layout'; import KiloClawLayout from '@/app/(app)/(tabs)/(1_kiloclaw)/_layout'; import OrganizationLayout from '@/app/(app)/(tabs)/(3_profile)/organization/_layout'; -import SecurityAgentScopeLayout from '@/app/(app)/(tabs)/(3_profile)/security-agent/[scope]/_layout'; +import SecurityAgentScopeLayout from '@/app/(app)/security-agent/[scope]/_layout'; import PrReviewNumberLayout from '@/app/(app)/pr-review/[owner]/[repo]/[number]/_layout'; import { AppUnlockProvider } from '@/lib/app-unlock-context'; diff --git a/apps/mobile/src/components/code-reviewer/bitbucket-overview.tsx b/apps/mobile/src/components/code-reviewer/bitbucket-overview.tsx index 2f1e0323bda..96ec14147ed 100644 --- a/apps/mobile/src/components/code-reviewer/bitbucket-overview.tsx +++ b/apps/mobile/src/components/code-reviewer/bitbucket-overview.tsx @@ -124,7 +124,7 @@ export function BitbucketOverview({ } const pushField = (field: string) => { - router.push(`/(app)/(tabs)/(3_profile)/code-reviewer/${scope}/bitbucket/${field}` as Href); + router.push(`/(app)/code-reviewer/${scope}/bitbucket/${field}` as Href); }; const data = config.data; diff --git a/apps/mobile/src/components/code-reviewer/full-surface-states.mounted.test.tsx b/apps/mobile/src/components/code-reviewer/full-surface-states.mounted.test.tsx index 3a043db05b9..5de0effb12c 100644 --- a/apps/mobile/src/components/code-reviewer/full-surface-states.mounted.test.tsx +++ b/apps/mobile/src/components/code-reviewer/full-surface-states.mounted.test.tsx @@ -3,7 +3,7 @@ import { createElement, type ReactElement, type ReactNode } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import '@/i18n'; -import ReposRoute from '@/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/repos'; +import ReposRoute from '@/app/(app)/code-reviewer/[scope]/[platform]/(edit)/repos'; import { ReviewListScreen } from './review-list-screen'; import { renderWithProviders, type RenderWithProvidersResult } from '@/test/render-with-providers'; @@ -293,7 +293,7 @@ describe('Recent review bodies', () => { const action = empty.props.action as ReactElement<{ onPress: () => void }>; action.props.onPress(); expect(state.push).toHaveBeenCalledWith( - `/(app)/(tabs)/(3_profile)/code-reviewer/personal${connected ? '/manual-review' : ''}` + `/(app)/code-reviewer/personal${connected ? '/manual-review' : ''}` ); unmount(); } diff --git a/apps/mobile/src/components/code-reviewer/manual-review-action-footer.tsx b/apps/mobile/src/components/code-reviewer/manual-review-action-footer.tsx index 7ba83f3d61f..ca35e33f5c5 100644 --- a/apps/mobile/src/components/code-reviewer/manual-review-action-footer.tsx +++ b/apps/mobile/src/components/code-reviewer/manual-review-action-footer.tsx @@ -10,20 +10,20 @@ import { useTabBarBottomPadding } from '@/components/tab-screen'; * The action used to be the scroll body's last child. At large display * densities the form is taller than the viewport, so it was clipped mid-label * at the scroll fold, against the tab bar (explorer manual-review finding, - * 2026-09-19). The footer keeps its own layout space clear of the tab bar, so - * the call to action is always fully legible. + * 2026-09-19). The footer reserves the screen's bottom clearance, so the call + * to action is always fully legible. * * The host wraps this footer and the scroll body in the keyboard-lift view, * which owns the bottom space while the keyboard is open. The footer's own * clearance therefore covers only what the lift does not: a keyboard taller - * than the tab bar drops it to 0, while a keyboard shorter than the tab bar - * (the hardware-keyboard IME bar is one navigation bar tall) keeps the rest, so - * the action never lands behind the tab bar (e1-fill, 2026-09-19). + * than the clearance drops it to 0, while a shorter one (the hardware-keyboard + * IME bar is one navigation bar tall) keeps the rest, so the action never lands + * behind the bottom edge (e1-fill, 2026-09-19). */ export function ManualReviewActionFooter({ children }: Readonly<{ children: ReactNode }>) { - const tabBarBottomPadding = useTabBarBottomPadding(); + const bottomClearance = useTabBarBottomPadding(); const keyboardLift = useKeyboardState(state => state.height); - const bottomPadding = Math.max(0, tabBarBottomPadding - keyboardLift); + const bottomPadding = Math.max(0, bottomClearance - keyboardLift); return ( {children} diff --git a/apps/mobile/src/components/code-reviewer/manual-review-screen.mounted.test.tsx b/apps/mobile/src/components/code-reviewer/manual-review-screen.mounted.test.tsx index 6f33722d457..afa3e25d557 100644 --- a/apps/mobile/src/components/code-reviewer/manual-review-screen.mounted.test.tsx +++ b/apps/mobile/src/components/code-reviewer/manual-review-screen.mounted.test.tsx @@ -4,7 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import '@/i18n'; import { act, type TestRenderer } from '@/test/renderer'; import { renderWithProviders } from '@/test/render-with-providers'; -import { getEffectiveTabBarHeight } from '@/lib/tab-bar-layout'; +import { TabBarLabelContext } from '@/lib/tab-bar-clearance'; import { ManualReviewScreen } from './manual-review-screen'; @@ -12,7 +12,7 @@ import { ManualReviewScreen } from './manual-review-screen'; // clipped mid-label at the scroll fold, under the tab bar, at 560 density. The // fix pins the action in a footer below the scroll view, so these tests assert // the structural guarantee (the action is not inside the scroll viewport) and -// the footer clearance (it sits a tab bar's height above the overlay). +// the footer clearance (it sits the screen's bottom clearance above the edge). const BOTTOM_INSET = 24; const TAB_SCREEN_BOTTOM_GAP = 16; @@ -172,8 +172,13 @@ function only(items: T[], what: string): T { } async function renderScreen() { + // The root stack publishes `null`: no tab bar under this screen. const { renderer, unmount } = await renderWithProviders( - createElement(ManualReviewScreen, { scope: 'personal' }) + createElement( + TabBarLabelContext, + { value: null }, + createElement(ManualReviewScreen, { scope: 'personal' }) + ) ); return { renderer, unmount }; } @@ -215,7 +220,7 @@ describe.each(['android', 'ios'] as const)('ManualReviewScreen primary action on platformState.OS = platform; }); - it('pins the start action outside the scroll viewport, clear of the tab bar', async () => { + it('pins the start action outside the scroll viewport, clear of the bottom inset', async () => { const { renderer, unmount } = await renderScreen(); const scrollViews = findAllOfType(renderer.root, 'ScrollView'); @@ -227,21 +232,15 @@ describe.each(['android', 'ios'] as const)('ManualReviewScreen primary action on const action = only(findAllOfType(renderer.root, 'Button'), 'primary action'); const [footerClearance] = paddingBottomsAbove(action); - expect(footerClearance).toBe( - getEffectiveTabBarHeight({ - bottomInset: BOTTOM_INSET, - platform, - fontScale: 1, - }) + TAB_SCREEN_BOTTOM_GAP - ); + expect(footerClearance).toBe(BOTTOM_INSET + TAB_SCREEN_BOTTOM_GAP); unmount(); }); - it('drops the tab bar clearance while the keyboard lifts the footer', async () => { + it('drops the bottom clearance while the keyboard lifts the footer', async () => { const { renderer, unmount } = await renderScreen(); - // A keyboard at least as tall as the tab bar. The footer hands the whole + // A keyboard at least as tall as the clearance. The footer hands the whole // bottom space to the lift view (`KeyboardAvoidingView`), which owns it // while the keyboard is open, so its own clearance collapses to 0. const keyboardLift = platform === 'android' ? 300 : 324; @@ -255,36 +254,29 @@ describe.each(['android', 'ios'] as const)('ManualReviewScreen primary action on expect(findAllOfType(renderer.root, 'KeyboardAvoidingView')).toHaveLength(1); // The provider clears the height when the keyboard hides; the footer - // restores its tab-bar clearance. + // restores its bottom clearance. act(() => { keyboardStore.setHeight(0); }); - expect(paddingBottomsAbove(action)).toEqual([ - getEffectiveTabBarHeight({ bottomInset: BOTTOM_INSET, platform, fontScale: 1 }) + - TAB_SCREEN_BOTTOM_GAP, - ]); + expect(paddingBottomsAbove(action)).toEqual([BOTTOM_INSET + TAB_SCREEN_BOTTOM_GAP]); unmount(); }); - it('keeps the tab bar clearance a short keyboard does not cover', async () => { + it('keeps the footer clearance a short keyboard does not cover', async () => { const { renderer, unmount } = await renderScreen(); - // The hardware-keyboard IME bar is one navigation bar tall; the footer keeps - // the difference so the action never lands behind the tab bar (e1-fill). - const keyboardLift = BOTTOM_INSET + BOTTOM_INSET; + // A keyboard shorter than the clearance leaves part of it uncovered; the + // footer keeps the difference so the action never lands behind the bottom + // edge (e1-fill). + const keyboardLift = BOTTOM_INSET; act(() => { keyboardStore.setHeight(keyboardLift); }); const action = only(findAllOfType(renderer.root, 'Button'), 'primary action'); - const footerClearance = getEffectiveTabBarHeight({ - bottomInset: BOTTOM_INSET, - platform, - fontScale: 1, - }); expect(paddingBottomsAbove(action)).toEqual([ - footerClearance + TAB_SCREEN_BOTTOM_GAP - keyboardLift, + BOTTOM_INSET + TAB_SCREEN_BOTTOM_GAP - keyboardLift, ]); unmount(); @@ -298,11 +290,8 @@ describe.each(['android', 'ios'] as const)('ManualReviewScreen primary action on const button = only(findAllOfType(renderer.root, 'Button'), 'primary action'); expect((button.props as { disabled: boolean }).disabled).toBe(true); // The footer is the sole clearance source now (the deleted lift layer is - // gone); at rest it reserves the tab bar height plus its final gap. - expect(paddingBottomsAbove(button)).toEqual([ - getEffectiveTabBarHeight({ bottomInset: BOTTOM_INSET, platform, fontScale: 1 }) + - TAB_SCREEN_BOTTOM_GAP, - ]); + // gone); at rest it reserves the screen's bottom clearance plus its final gap. + expect(paddingBottomsAbove(button)).toEqual([BOTTOM_INSET + TAB_SCREEN_BOTTOM_GAP]); unmount(); }); @@ -312,11 +301,8 @@ describe.each(['android', 'ios'] as const)('ManualReviewScreen primary action on const button = only(findAllOfType(renderer.root, 'Button'), 'primary action'); expect((button.props as { loading: boolean }).loading).toBe(true); // The footer is the sole clearance source now (the deleted lift layer is - // gone); at rest it reserves the tab bar height plus its final gap. - expect(paddingBottomsAbove(button)).toEqual([ - getEffectiveTabBarHeight({ bottomInset: BOTTOM_INSET, platform, fontScale: 1 }) + - TAB_SCREEN_BOTTOM_GAP, - ]); + // gone); at rest it reserves the screen's bottom clearance plus its final gap. + expect(paddingBottomsAbove(button)).toEqual([BOTTOM_INSET + TAB_SCREEN_BOTTOM_GAP]); unmount(); }); @@ -348,9 +334,7 @@ describe.each(['android', 'ios'] as const)('ManualReviewScreen primary action on act(() => { action.props.onPress(); }); - expect(status.push).toHaveBeenCalledWith( - '/(app)/(tabs)/(3_profile)/code-reviewer/personal/github' - ); + expect(status.push).toHaveBeenCalledWith('/(app)/code-reviewer/personal/github'); expect(findAllOfType(renderer.root, 'ScrollView')).toHaveLength(0); unmount(); }); diff --git a/apps/mobile/src/components/code-reviewer/manual-review-screen.tsx b/apps/mobile/src/components/code-reviewer/manual-review-screen.tsx index a85317e7d99..18618ab2c6a 100644 --- a/apps/mobile/src/components/code-reviewer/manual-review-screen.tsx +++ b/apps/mobile/src/components/code-reviewer/manual-review-screen.tsx @@ -119,9 +119,7 @@ export function ManualReviewScreen({ scope }: Readonly<{ scope: string }>) { onSuccess: ({ reviewId }) => { settle(null); void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); - router.replace( - `/(app)/(tabs)/(3_profile)/code-reviewer/${scope}/reviews/${reviewId}` as Href - ); + router.replace(`/(app)/code-reviewer/${scope}/reviews/${reviewId}` as Href); }, onError: error => { // A client-deadline rejection is the stall, not a server reason: @@ -205,7 +203,7 @@ export function ManualReviewScreen({ scope }: Readonly<{ scope: string }>) {