From 511a7460b0df115b20aca458011067f7ecf7448d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?=
<4062236+iscekic@users.noreply.github.com>
Date: Wed, 21 Oct 2026 21:20:25 +0000
Subject: [PATCH 1/7] fix(mobile): open Code Reviewer and Security Agent above
the tabs
The Home Explore shortcuts for Code Reviewer and Security Agent pushed
routes that live inside the Profile tab's stack, so expo-router switched
the tab navigator and the bottom bar drew Profile as the selected tab
while those screens were on top.
Move both route trees out of `(tabs)/(3_profile)` into the root stack,
like PR Review: the screens open above the tab bar, no tab is marked
selected while they are up, and going back returns to the tab the user
came from. URL paths are unchanged because group segments are not part
of a URL, and the shared universal-link table now targets the new
paths. A last-route record mirrored by an older build is read at the
screen's current path instead of an unmatched one.
The content clearance follows the same rule: the root stack publishes
that no tab bar sits under its screens, so their scroll views reserve
the safe-area inset instead of a bar height.
---
apps/mobile/src/app/(app)/_layout.tsx | 181 +++++++++---------
.../[scope]/[platform]/(edit)/_layout.tsx | 0
.../(edit)/focus-areas.mounted.test.tsx | 0
.../[scope]/[platform]/(edit)/focus-areas.tsx | 0
.../[scope]/[platform]/(edit)/gate.tsx | 0
.../[platform]/(edit)/instructions.tsx | 0
.../[scope]/[platform]/(edit)/repos.tsx | 0
.../[scope]/[platform]/(edit)/style.tsx | 0
.../[scope]/[platform]/_layout.tsx | 2 +-
.../[scope]/[platform]/index.tsx | 0
.../[scope]/index.mounted.test.tsx | 0
.../code-reviewer/[scope]/index.tsx | 0
.../[scope]/manual-review.mounted.test.tsx | 0
.../code-reviewer/[scope]/manual-review.tsx | 0
.../code-reviewer/[scope]/review-memory.tsx | 0
.../[scope]/reviews/[id].mounted.test.tsx | 8 +-
.../code-reviewer/[scope]/reviews/[id].tsx | 4 +-
.../[scope]/reviews/index.mounted.test.tsx | 0
.../code-reviewer/[scope]/reviews/index.tsx | 0
.../(3_profile) => }/code-reviewer/index.tsx | 0
.../security-agent/[scope]/_layout.tsx | 2 +-
.../security-agent/[scope]/audit-report.tsx | 0
.../security-agent/[scope]/dismiss/[id].tsx | 4 +-
.../security-agent/[scope]/filter.tsx | 0
.../security-agent/[scope]/findings/[id].tsx | 4 +-
.../security-agent/[scope]/findings/index.tsx | 0
.../security-agent/[scope]/index.tsx | 0
.../[scope]/settings/analysis.tsx | 0
.../[scope]/settings/automation.tsx | 0
.../security-agent/[scope]/settings/index.tsx | 0
.../[scope]/settings/notifications.tsx | 0
.../[scope]/settings/repositories.tsx | 0
.../security-agent/[scope]/settings/sla.tsx | 0
.../(3_profile) => }/security-agent/index.tsx | 0
.../app-unlock-screen.mounted.test.tsx | 2 +-
.../code-reviewer/bitbucket-overview.tsx | 2 +-
.../full-surface-states.mounted.test.tsx | 4 +-
.../manual-review-action-footer.tsx | 14 +-
.../manual-review-screen.mounted.test.tsx | 66 +++----
.../code-reviewer/manual-review-screen.tsx | 6 +-
.../code-reviewer/platform-list-screen.tsx | 10 +-
.../platform-overview-screen.tsx | 6 +-
.../review-detail-screen.mounted.test.tsx | 4 +-
.../code-reviewer/review-detail-screen.tsx | 4 +-
.../code-reviewer/review-list-screen.tsx | 8 +-
.../code-reviewer/scope-list-screen.tsx | 2 +-
...ail-screen.analysis-crash.mounted.test.tsx | 2 +-
.../finding-detail-screen.mounted.test.tsx | 2 +-
.../finding-list-screen.mounted.test.tsx | 2 +-
.../components/tab-screen.mounted.test.tsx | 50 ++++-
apps/mobile/src/components/tab-screen.tsx | 10 +-
apps/mobile/src/lib/deep-link-handler.test.ts | 6 +-
apps/mobile/src/lib/deep-link-launch.test.ts | 4 +-
.../src/lib/hooks/use-reviewer-permission.ts | 2 +-
apps/mobile/src/lib/last-route.test.ts | 19 ++
apps/mobile/src/lib/last-route.ts | 23 ++-
.../src/lib/notification-actions.test.ts | 2 +-
.../src/lib/notification-path-schema.test.ts | 4 +-
apps/mobile/src/lib/notification-path.test.ts | 8 +-
apps/mobile/src/lib/notifications.test.ts | 2 +-
.../mobile/src/lib/pending-navigation.test.ts | 6 +-
.../src/lib/profile-agent-navigation.test.ts | 14 +-
.../src/lib/profile-agent-navigation.ts | 2 +-
apps/mobile/src/lib/security-agent.test.ts | 4 +-
apps/mobile/src/lib/security-agent.ts | 2 +-
...y-notification-navigation.mounted.test.tsx | 8 +-
.../src/lib/system-search-collect.test.ts | 6 +-
.../src/lib/system-search-entries.test.ts | 32 ++--
apps/mobile/src/lib/system-search-entries.ts | 8 +-
apps/mobile/src/lib/system-search-families.ts | 12 +-
.../src/lib/system-search-route.test.ts | 2 +-
apps/mobile/src/lib/tab-bar-clearance.ts | 31 +--
apps/mobile/src/lib/tab-press.test.ts | 12 +-
.../src/universal-links/routes.test.ts | 38 ++--
.../app-shared/src/universal-links/routes.ts | 16 +-
75 files changed, 342 insertions(+), 320 deletions(-)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/[platform]/(edit)/_layout.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/[platform]/(edit)/focus-areas.mounted.test.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/[platform]/(edit)/focus-areas.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/[platform]/(edit)/gate.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/[platform]/(edit)/instructions.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/[platform]/(edit)/repos.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/[platform]/(edit)/style.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/[platform]/_layout.tsx (87%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/[platform]/index.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/index.mounted.test.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/index.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/manual-review.mounted.test.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/manual-review.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/review-memory.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/reviews/[id].mounted.test.tsx (94%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/reviews/[id].tsx (85%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/reviews/index.mounted.test.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/[scope]/reviews/index.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/code-reviewer/index.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/_layout.tsx (95%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/audit-report.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/dismiss/[id].tsx (82%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/filter.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/findings/[id].tsx (82%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/findings/index.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/index.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/settings/analysis.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/settings/automation.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/settings/index.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/settings/notifications.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/settings/repositories.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/[scope]/settings/sla.tsx (100%)
rename apps/mobile/src/app/(app)/{(tabs)/(3_profile) => }/security-agent/index.tsx (100%)
diff --git a/apps/mobile/src/app/(app)/_layout.tsx b/apps/mobile/src/app/(app)/_layout.tsx
index 84a5f5ffc6b..eddfdf19f24 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,96 +135,100 @@ 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.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 100%
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
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 100%
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
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 100%
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
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 100%
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
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 94%
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..eac9ee559a4 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
@@ -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..022684c9f6f 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)/(tabs)/(3_profile)'
) 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 100%
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
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 100%
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
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 95%
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 077ade23fb4..f97693f8c57 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
@@ -18,7 +18,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 b2e3225661b..b2f74193c58 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';
@@ -258,7 +258,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 }>) {
diff --git a/apps/mobile/src/components/code-reviewer/platform-overview-screen.tsx b/apps/mobile/src/components/code-reviewer/platform-overview-screen.tsx
index 87fd297cbc8..70b66a023ac 100644
--- a/apps/mobile/src/components/code-reviewer/platform-overview-screen.tsx
+++ b/apps/mobile/src/components/code-reviewer/platform-overview-screen.tsx
@@ -162,7 +162,7 @@ export function PlatformOverviewScreen({
}
const pushField = (field: string) => {
- router.push(`/(app)/(tabs)/(3_profile)/code-reviewer/${scope}/${platform}/${field}` as Href);
+ router.push(`/(app)/code-reviewer/${scope}/${platform}/${field}` as Href);
};
const data = config.data;
@@ -194,9 +194,7 @@ export function PlatformOverviewScreen({
onOpenReviewMemory:
platform === 'github'
? () => {
- router.push(
- `/(app)/(tabs)/(3_profile)/code-reviewer/${scope}/review-memory` as Href
- );
+ router.push(`/(app)/code-reviewer/${scope}/review-memory` as Href);
}
: undefined,
});
diff --git a/apps/mobile/src/components/code-reviewer/review-detail-screen.mounted.test.tsx b/apps/mobile/src/components/code-reviewer/review-detail-screen.mounted.test.tsx
index c88d3403c00..6dbec517545 100644
--- a/apps/mobile/src/components/code-reviewer/review-detail-screen.mounted.test.tsx
+++ b/apps/mobile/src/components/code-reviewer/review-detail-screen.mounted.test.tsx
@@ -1354,9 +1354,7 @@ describe('ReviewDetailScreen retry when Code Reviewer cannot run', () => {
act(() => {
fixSettings?.onPress?.();
});
- expect(routerMock.push).toHaveBeenCalledWith(
- '/(app)/(tabs)/(3_profile)/code-reviewer/personal/github'
- );
+ expect(routerMock.push).toHaveBeenCalledWith('/(app)/code-reviewer/personal/github');
});
it('links to the settings when the configuration needs attention', () => {
diff --git a/apps/mobile/src/components/code-reviewer/review-detail-screen.tsx b/apps/mobile/src/components/code-reviewer/review-detail-screen.tsx
index ecf5f2e76a8..d41c0cd70e7 100644
--- a/apps/mobile/src/components/code-reviewer/review-detail-screen.tsx
+++ b/apps/mobile/src/components/code-reviewer/review-detail-screen.tsx
@@ -349,9 +349,7 @@ export function ReviewDetailScreen({