From 7b4658e644f76ca911bb0f2a2fae70eda4d38b97 Mon Sep 17 00:00:00 2001
From: Alaister Young <10985857+alaister@users.noreply.github.com>
Date: Fri, 8 May 2026 18:06:43 +0800
Subject: [PATCH] feat(studio): land BranchLayout shell + 2 leaves
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
routes/project/$ref/branches.tsx renders BranchLayout only — each leaf
keeps its own PageLayout because the title + primary/secondary actions
differ between Branches and Merge Requests, and the actions use hooks
(useAppStateSnapshot, useAsyncCheckPermissions) so they can't be moved
to staticData.
To avoid duplicating the wrapper logic in route files, the page-side
wrappers are hoisted/exported:
- pages/.../branches/index.tsx: \`BranchesPageWrapper\` was inline inside
getLayout; lifted to a top-level \`export const\` taking children, and
getLayout now uses it via \`{page}\`.
Both runtimes still see identical layout.
- pages/.../branches/merge-requests.tsx: \`MergeRequestsPageWrapper\` was
already top-level — just added \`export\`.
The route files import \`Page, { PageWrapper }\` from each page and render
\`\`.
Migration doc updated with the BranchLayout-only delta.
---
apps/studio/TANSTACK_MIGRATION.md | 6 +-
.../pages/project/[ref]/branches/index.tsx | 119 +++++++++---------
.../project/[ref]/branches/merge-requests.tsx | 2 +-
apps/studio/routeTree.gen.ts | 71 +++++++++++
apps/studio/routes/project/$ref/branches.tsx | 20 +++
.../routes/project/$ref/branches/index.tsx | 15 +++
.../project/$ref/branches/merge-requests.tsx | 17 +++
7 files changed, 187 insertions(+), 63 deletions(-)
create mode 100644 apps/studio/routes/project/$ref/branches.tsx
create mode 100644 apps/studio/routes/project/$ref/branches/index.tsx
create mode 100644 apps/studio/routes/project/$ref/branches/merge-requests.tsx
diff --git a/apps/studio/TANSTACK_MIGRATION.md b/apps/studio/TANSTACK_MIGRATION.md
index 2905911c698..548f5f6b8e2 100644
--- a/apps/studio/TANSTACK_MIGRATION.md
+++ b/apps/studio/TANSTACK_MIGRATION.md
@@ -77,7 +77,7 @@ These are the layout-only TanStack files. Most hold a single product layout comp
- [x] `routes/project/$ref/storage.tsx` — StorageLayout + StorageBucketsLayout (reads `storageLayoutTitle`, optional `skipStorageBucketsLayout`, `storageBucketsLayoutTitle`, `storageBucketsLayoutHideSubtitle` from leaf `staticData`). **Delta vs plan:** the shell wraps in BOTH StorageLayout and StorageBucketsLayout by default — every storage page except bucket-detail pages uses both. Bucket-detail pages set `skipStorageBucketsLayout: true`. `/storage/s3` uses `storageBucketsLayout{Title,HideSubtitle}` to override the inner header.
- [x] `routes/project/$ref/realtime.tsx` — RealtimeLayout (reads `realtimeLayoutTitle` from leaf `staticData`)
- [ ] `routes/project/$ref/functions.tsx` — EdgeFunctionsLayout
-- [ ] `routes/project/$ref/branches.tsx` — BranchLayout + PageLayout
+- [x] `routes/project/$ref/branches.tsx` — BranchLayout only. **Delta vs plan:** the per-page `PageLayout` (with different titles + primary/secondary actions) stays in each leaf. Hoisted `BranchesPageWrapper` and `MergeRequestsPageWrapper` to top-level exports in their respective `pages/...` files so the route files can import + re-use the same wrapping.
- [ ] `routes/project/$ref/logs.tsx` — LogsLayout
- [ ] `routes/project/$ref/observability.tsx` — ObservabilityLayout
- [ ] `routes/project/$ref/advisors.tsx` — AdvisorsLayout
@@ -226,8 +226,8 @@ These are the layout-only TanStack files. Most hold a single product layout comp
### Project shell — `/branches/*`
-- [ ] `routes/project/$ref/branches/index.tsx` ← `pages/project/[ref]/branches/index.tsx`
-- [ ] `routes/project/$ref/branches/merge-requests.tsx` ← `pages/project/[ref]/branches/merge-requests.tsx`
+- [x] A `routes/project/$ref/branches/index.tsx` ← `pages/project/[ref]/branches/index.tsx` (route wraps in exported `BranchesPageWrapper` to preserve the page's `PageLayout` + Create-branch action)
+- [x] A `routes/project/$ref/branches/merge-requests.tsx` ← `pages/project/[ref]/branches/merge-requests.tsx` (route wraps in exported `MergeRequestsPageWrapper`)
### Project shell — `/logs/*`
diff --git a/apps/studio/pages/project/[ref]/branches/index.tsx b/apps/studio/pages/project/[ref]/branches/index.tsx
index e0994a0fafa..fe98e0a0a24 100644
--- a/apps/studio/pages/project/[ref]/branches/index.tsx
+++ b/apps/studio/pages/project/[ref]/branches/index.tsx
@@ -3,7 +3,7 @@ import { useParams } from 'common'
import { partition } from 'lodash'
import { MessageCircle } from 'lucide-react'
import { useRouter } from 'next/router'
-import { useState } from 'react'
+import { useState, type PropsWithChildren } from 'react'
import { toast } from 'sonner'
import { Button } from 'ui'
@@ -193,69 +193,70 @@ const BranchesPage: NextPageWithLayout = () => {
)
}
-BranchesPage.getLayout = (page) => {
- const BranchesPageWrapper = () => {
- const snap = useAppStateSnapshot()
- const { can: canCreateBranches } = useAsyncCheckPermissions(
- PermissionAction.CREATE,
- 'preview_branches',
- {
- resource: { is_default: false },
- }
- )
+// Hoisted out of `getLayout` so the TanStack route can import it
+// directly. Same shape and identical body as before — accepts the page
+// content as `children` instead of capturing it from a closure.
+export const BranchesPageWrapper = ({ children }: PropsWithChildren) => {
+ const snap = useAppStateSnapshot()
+ const { can: canCreateBranches } = useAsyncCheckPermissions(
+ PermissionAction.CREATE,
+ 'preview_branches',
+ {
+ resource: { is_default: false },
+ }
+ )
- const primaryActions = (
- snap.setShowCreateBranchModal(true)}
- tooltip={{
- content: {
- side: 'bottom',
- text: !canCreateBranches
- ? 'You need additional permissions to create branches'
- : undefined,
- },
- }}
- >
- Create branch
-
- )
+ const primaryActions = (
+ snap.setShowCreateBranchModal(true)}
+ tooltip={{
+ content: {
+ side: 'bottom',
+ text: !canCreateBranches
+ ? 'You need additional permissions to create branches'
+ : undefined,
+ },
+ }}
+ >
+ Create branch
+
+ )
- const secondaryActions = (
-
- )
-
- return (
-
- {page}
-
- )
- }
+ const secondaryActions = (
+
+ )
return (
-
-
-
-
-
+
+ {children}
+
)
}
+BranchesPage.getLayout = (page) => (
+
+
+ {page}
+
+
+)
+
export default BranchesPage
diff --git a/apps/studio/pages/project/[ref]/branches/merge-requests.tsx b/apps/studio/pages/project/[ref]/branches/merge-requests.tsx
index 509fc9f9276..213dd05ec95 100644
--- a/apps/studio/pages/project/[ref]/branches/merge-requests.tsx
+++ b/apps/studio/pages/project/[ref]/branches/merge-requests.tsx
@@ -309,7 +309,7 @@ const MergeRequestsPage: NextPageWithLayout = () => {
)
}
-const MergeRequestsPageWrapper = ({ children }: PropsWithChildren<{}>) => {
+export const MergeRequestsPageWrapper = ({ children }: PropsWithChildren<{}>) => {
const router = useRouter()
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
diff --git a/apps/studio/routeTree.gen.ts b/apps/studio/routeTree.gen.ts
index 527b2e0c053..107e0ff6a80 100644
--- a/apps/studio/routeTree.gen.ts
+++ b/apps/studio/routeTree.gen.ts
@@ -49,6 +49,7 @@ import { Route as ProjectRefSqlRouteImport } from './routes/project/$ref/sql'
import { Route as ProjectRefRealtimeRouteImport } from './routes/project/$ref/realtime'
import { Route as ProjectRefEditorRouteImport } from './routes/project/$ref/editor'
import { Route as ProjectRefDatabaseRouteImport } from './routes/project/$ref/database'
+import { Route as ProjectRefBranchesRouteImport } from './routes/project/$ref/branches'
import { Route as ProjectRefAuthRouteImport } from './routes/project/$ref/auth'
import { Route as ApiIntegrationsStripeSyncRouteImport } from './routes/api/integrations/stripe-sync'
import { Route as ApiEdgeFunctionsTestRouteImport } from './routes/api/edge-functions/test'
@@ -60,6 +61,7 @@ import { Route as AppAccountMeRouteImport } from './routes/_app/account/me'
import { Route as AppAccountAuditRouteImport } from './routes/_app/account/audit'
import { Route as ProjectRefSqlIndexRouteImport } from './routes/project/$ref/sql/index'
import { Route as ProjectRefEditorIndexRouteImport } from './routes/project/$ref/editor/index'
+import { Route as ProjectRefBranchesIndexRouteImport } from './routes/project/$ref/branches/index'
import { Route as ApiPlatformProjectsIndexRouteImport } from './routes/api/platform/projects/index'
import { Route as ApiPlatformProfileIndexRouteImport } from './routes/api/platform/profile/index'
import { Route as ApiPlatformOrganizationsIndexRouteImport } from './routes/api/platform/organizations/index'
@@ -84,6 +86,7 @@ import { Route as ProjectRefDatabaseIndexesRouteImport } from './routes/project/
import { Route as ProjectRefDatabaseFunctionsRouteImport } from './routes/project/$ref/database/functions'
import { Route as ProjectRefDatabaseExtensionsRouteImport } from './routes/project/$ref/database/extensions'
import { Route as ProjectRefDatabaseColumnPrivilegesRouteImport } from './routes/project/$ref/database/column-privileges'
+import { Route as ProjectRefBranchesMergeRequestsRouteImport } from './routes/project/$ref/branches/merge-requests'
import { Route as ProjectRefAuthUsersRouteImport } from './routes/project/$ref/auth/users'
import { Route as ProjectRefAuthUrlConfigurationRouteImport } from './routes/project/$ref/auth/url-configuration'
import { Route as ProjectRefAuthThirdPartyRouteImport } from './routes/project/$ref/auth/third-party'
@@ -413,6 +416,11 @@ const ProjectRefDatabaseRoute = ProjectRefDatabaseRouteImport.update({
path: '/database',
getParentRoute: () => ProjectRefRoute,
} as any)
+const ProjectRefBranchesRoute = ProjectRefBranchesRouteImport.update({
+ id: '/branches',
+ path: '/branches',
+ getParentRoute: () => ProjectRefRoute,
+} as any)
const ProjectRefAuthRoute = ProjectRefAuthRouteImport.update({
id: '/auth',
path: '/auth',
@@ -469,6 +477,11 @@ const ProjectRefEditorIndexRoute = ProjectRefEditorIndexRouteImport.update({
path: '/',
getParentRoute: () => ProjectRefEditorRoute,
} as any)
+const ProjectRefBranchesIndexRoute = ProjectRefBranchesIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => ProjectRefBranchesRoute,
+} as any)
const ApiPlatformProjectsIndexRoute =
ApiPlatformProjectsIndexRouteImport.update({
id: '/api/platform/projects/',
@@ -603,6 +616,12 @@ const ProjectRefDatabaseColumnPrivilegesRoute =
path: '/column-privileges',
getParentRoute: () => ProjectRefDatabaseRoute,
} as any)
+const ProjectRefBranchesMergeRequestsRoute =
+ ProjectRefBranchesMergeRequestsRouteImport.update({
+ id: '/merge-requests',
+ path: '/merge-requests',
+ getParentRoute: () => ProjectRefBranchesRoute,
+ } as any)
const ProjectRefAuthUsersRoute = ProjectRefAuthUsersRouteImport.update({
id: '/users',
path: '/users',
@@ -1370,6 +1389,7 @@ export interface FileRoutesByFullPath {
'/api/edge-functions/test': typeof ApiEdgeFunctionsTestRoute
'/api/integrations/stripe-sync': typeof ApiIntegrationsStripeSyncRoute
'/project/$ref/auth': typeof ProjectRefAuthRouteWithChildren
+ '/project/$ref/branches': typeof ProjectRefBranchesRouteWithChildren
'/project/$ref/database': typeof ProjectRefDatabaseRouteWithChildren
'/project/$ref/editor': typeof ProjectRefEditorRouteWithChildren
'/project/$ref/realtime': typeof ProjectRefRealtimeRouteWithChildren
@@ -1418,6 +1438,7 @@ export interface FileRoutesByFullPath {
'/project/$ref/auth/third-party': typeof ProjectRefAuthThirdPartyRoute
'/project/$ref/auth/url-configuration': typeof ProjectRefAuthUrlConfigurationRoute
'/project/$ref/auth/users': typeof ProjectRefAuthUsersRoute
+ '/project/$ref/branches/merge-requests': typeof ProjectRefBranchesMergeRequestsRoute
'/project/$ref/database/column-privileges': typeof ProjectRefDatabaseColumnPrivilegesRoute
'/project/$ref/database/extensions': typeof ProjectRefDatabaseExtensionsRoute
'/project/$ref/database/functions': typeof ProjectRefDatabaseFunctionsRoute
@@ -1442,6 +1463,7 @@ export interface FileRoutesByFullPath {
'/api/platform/organizations/': typeof ApiPlatformOrganizationsIndexRoute
'/api/platform/profile/': typeof ApiPlatformProfileIndexRoute
'/api/platform/projects/': typeof ApiPlatformProjectsIndexRoute
+ '/project/$ref/branches/': typeof ProjectRefBranchesIndexRoute
'/project/$ref/editor/': typeof ProjectRefEditorIndexRoute
'/project/$ref/sql/': typeof ProjectRefSqlIndexRoute
'/org/$slug/webhooks/$endpointId': typeof AppOrgSlugWebhooksEndpointIdRoute
@@ -1616,6 +1638,7 @@ export interface FileRoutesByTo {
'/project/$ref/auth/third-party': typeof ProjectRefAuthThirdPartyRoute
'/project/$ref/auth/url-configuration': typeof ProjectRefAuthUrlConfigurationRoute
'/project/$ref/auth/users': typeof ProjectRefAuthUsersRoute
+ '/project/$ref/branches/merge-requests': typeof ProjectRefBranchesMergeRequestsRoute
'/project/$ref/database/column-privileges': typeof ProjectRefDatabaseColumnPrivilegesRoute
'/project/$ref/database/extensions': typeof ProjectRefDatabaseExtensionsRoute
'/project/$ref/database/functions': typeof ProjectRefDatabaseFunctionsRoute
@@ -1639,6 +1662,7 @@ export interface FileRoutesByTo {
'/api/platform/organizations': typeof ApiPlatformOrganizationsIndexRoute
'/api/platform/profile': typeof ApiPlatformProfileIndexRoute
'/api/platform/projects': typeof ApiPlatformProjectsIndexRoute
+ '/project/$ref/branches': typeof ProjectRefBranchesIndexRoute
'/project/$ref/editor': typeof ProjectRefEditorIndexRoute
'/project/$ref/sql': typeof ProjectRefSqlIndexRoute
'/org/$slug/webhooks/$endpointId': typeof AppOrgSlugWebhooksEndpointIdRoute
@@ -1772,6 +1796,7 @@ export interface FileRoutesById {
'/api/edge-functions/test': typeof ApiEdgeFunctionsTestRoute
'/api/integrations/stripe-sync': typeof ApiIntegrationsStripeSyncRoute
'/project/$ref/auth': typeof ProjectRefAuthRouteWithChildren
+ '/project/$ref/branches': typeof ProjectRefBranchesRouteWithChildren
'/project/$ref/database': typeof ProjectRefDatabaseRouteWithChildren
'/project/$ref/editor': typeof ProjectRefEditorRouteWithChildren
'/project/$ref/realtime': typeof ProjectRefRealtimeRouteWithChildren
@@ -1820,6 +1845,7 @@ export interface FileRoutesById {
'/project/$ref/auth/third-party': typeof ProjectRefAuthThirdPartyRoute
'/project/$ref/auth/url-configuration': typeof ProjectRefAuthUrlConfigurationRoute
'/project/$ref/auth/users': typeof ProjectRefAuthUsersRoute
+ '/project/$ref/branches/merge-requests': typeof ProjectRefBranchesMergeRequestsRoute
'/project/$ref/database/column-privileges': typeof ProjectRefDatabaseColumnPrivilegesRoute
'/project/$ref/database/extensions': typeof ProjectRefDatabaseExtensionsRoute
'/project/$ref/database/functions': typeof ProjectRefDatabaseFunctionsRoute
@@ -1844,6 +1870,7 @@ export interface FileRoutesById {
'/api/platform/organizations/': typeof ApiPlatformOrganizationsIndexRoute
'/api/platform/profile/': typeof ApiPlatformProfileIndexRoute
'/api/platform/projects/': typeof ApiPlatformProjectsIndexRoute
+ '/project/$ref/branches/': typeof ProjectRefBranchesIndexRoute
'/project/$ref/editor/': typeof ProjectRefEditorIndexRoute
'/project/$ref/sql/': typeof ProjectRefSqlIndexRoute
'/_app/org/$slug/webhooks/$endpointId': typeof AppOrgSlugWebhooksEndpointIdRoute
@@ -1976,6 +2003,7 @@ export interface FileRouteTypes {
| '/api/edge-functions/test'
| '/api/integrations/stripe-sync'
| '/project/$ref/auth'
+ | '/project/$ref/branches'
| '/project/$ref/database'
| '/project/$ref/editor'
| '/project/$ref/realtime'
@@ -2024,6 +2052,7 @@ export interface FileRouteTypes {
| '/project/$ref/auth/third-party'
| '/project/$ref/auth/url-configuration'
| '/project/$ref/auth/users'
+ | '/project/$ref/branches/merge-requests'
| '/project/$ref/database/column-privileges'
| '/project/$ref/database/extensions'
| '/project/$ref/database/functions'
@@ -2048,6 +2077,7 @@ export interface FileRouteTypes {
| '/api/platform/organizations/'
| '/api/platform/profile/'
| '/api/platform/projects/'
+ | '/project/$ref/branches/'
| '/project/$ref/editor/'
| '/project/$ref/sql/'
| '/org/$slug/webhooks/$endpointId'
@@ -2222,6 +2252,7 @@ export interface FileRouteTypes {
| '/project/$ref/auth/third-party'
| '/project/$ref/auth/url-configuration'
| '/project/$ref/auth/users'
+ | '/project/$ref/branches/merge-requests'
| '/project/$ref/database/column-privileges'
| '/project/$ref/database/extensions'
| '/project/$ref/database/functions'
@@ -2245,6 +2276,7 @@ export interface FileRouteTypes {
| '/api/platform/organizations'
| '/api/platform/profile'
| '/api/platform/projects'
+ | '/project/$ref/branches'
| '/project/$ref/editor'
| '/project/$ref/sql'
| '/org/$slug/webhooks/$endpointId'
@@ -2377,6 +2409,7 @@ export interface FileRouteTypes {
| '/api/edge-functions/test'
| '/api/integrations/stripe-sync'
| '/project/$ref/auth'
+ | '/project/$ref/branches'
| '/project/$ref/database'
| '/project/$ref/editor'
| '/project/$ref/realtime'
@@ -2425,6 +2458,7 @@ export interface FileRouteTypes {
| '/project/$ref/auth/third-party'
| '/project/$ref/auth/url-configuration'
| '/project/$ref/auth/users'
+ | '/project/$ref/branches/merge-requests'
| '/project/$ref/database/column-privileges'
| '/project/$ref/database/extensions'
| '/project/$ref/database/functions'
@@ -2449,6 +2483,7 @@ export interface FileRouteTypes {
| '/api/platform/organizations/'
| '/api/platform/profile/'
| '/api/platform/projects/'
+ | '/project/$ref/branches/'
| '/project/$ref/editor/'
| '/project/$ref/sql/'
| '/_app/org/$slug/webhooks/$endpointId'
@@ -2925,6 +2960,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ProjectRefDatabaseRouteImport
parentRoute: typeof ProjectRefRoute
}
+ '/project/$ref/branches': {
+ id: '/project/$ref/branches'
+ path: '/branches'
+ fullPath: '/project/$ref/branches'
+ preLoaderRoute: typeof ProjectRefBranchesRouteImport
+ parentRoute: typeof ProjectRefRoute
+ }
'/project/$ref/auth': {
id: '/project/$ref/auth'
path: '/auth'
@@ -3002,6 +3044,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ProjectRefEditorIndexRouteImport
parentRoute: typeof ProjectRefEditorRoute
}
+ '/project/$ref/branches/': {
+ id: '/project/$ref/branches/'
+ path: '/'
+ fullPath: '/project/$ref/branches/'
+ preLoaderRoute: typeof ProjectRefBranchesIndexRouteImport
+ parentRoute: typeof ProjectRefBranchesRoute
+ }
'/api/platform/projects/': {
id: '/api/platform/projects/'
path: '/api/platform/projects'
@@ -3170,6 +3219,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ProjectRefDatabaseColumnPrivilegesRouteImport
parentRoute: typeof ProjectRefDatabaseRoute
}
+ '/project/$ref/branches/merge-requests': {
+ id: '/project/$ref/branches/merge-requests'
+ path: '/merge-requests'
+ fullPath: '/project/$ref/branches/merge-requests'
+ preLoaderRoute: typeof ProjectRefBranchesMergeRequestsRouteImport
+ parentRoute: typeof ProjectRefBranchesRoute
+ }
'/project/$ref/auth/users': {
id: '/project/$ref/auth/users'
path: '/users'
@@ -4212,6 +4268,19 @@ const ProjectRefAuthRouteWithChildren = ProjectRefAuthRoute._addFileChildren(
ProjectRefAuthRouteChildren,
)
+interface ProjectRefBranchesRouteChildren {
+ ProjectRefBranchesMergeRequestsRoute: typeof ProjectRefBranchesMergeRequestsRoute
+ ProjectRefBranchesIndexRoute: typeof ProjectRefBranchesIndexRoute
+}
+
+const ProjectRefBranchesRouteChildren: ProjectRefBranchesRouteChildren = {
+ ProjectRefBranchesMergeRequestsRoute: ProjectRefBranchesMergeRequestsRoute,
+ ProjectRefBranchesIndexRoute: ProjectRefBranchesIndexRoute,
+}
+
+const ProjectRefBranchesRouteWithChildren =
+ ProjectRefBranchesRoute._addFileChildren(ProjectRefBranchesRouteChildren)
+
interface ProjectRefDatabaseTriggersRouteChildren {
ProjectRefDatabaseTriggersDataRoute: typeof ProjectRefDatabaseTriggersDataRoute
ProjectRefDatabaseTriggersEventRoute: typeof ProjectRefDatabaseTriggersEventRoute
@@ -4366,6 +4435,7 @@ const ProjectRefStorageRouteWithChildren =
interface ProjectRefRouteChildren {
ProjectRefAuthRoute: typeof ProjectRefAuthRouteWithChildren
+ ProjectRefBranchesRoute: typeof ProjectRefBranchesRouteWithChildren
ProjectRefDatabaseRoute: typeof ProjectRefDatabaseRouteWithChildren
ProjectRefEditorRoute: typeof ProjectRefEditorRouteWithChildren
ProjectRefRealtimeRoute: typeof ProjectRefRealtimeRouteWithChildren
@@ -4376,6 +4446,7 @@ interface ProjectRefRouteChildren {
const ProjectRefRouteChildren: ProjectRefRouteChildren = {
ProjectRefAuthRoute: ProjectRefAuthRouteWithChildren,
+ ProjectRefBranchesRoute: ProjectRefBranchesRouteWithChildren,
ProjectRefDatabaseRoute: ProjectRefDatabaseRouteWithChildren,
ProjectRefEditorRoute: ProjectRefEditorRouteWithChildren,
ProjectRefRealtimeRoute: ProjectRefRealtimeRouteWithChildren,
diff --git a/apps/studio/routes/project/$ref/branches.tsx b/apps/studio/routes/project/$ref/branches.tsx
new file mode 100644
index 00000000000..5442d04064b
--- /dev/null
+++ b/apps/studio/routes/project/$ref/branches.tsx
@@ -0,0 +1,20 @@
+import { createFileRoute, Outlet } from '@tanstack/react-router'
+
+import BranchLayout from '@/components/layouts/BranchLayout/BranchLayout'
+
+export const Route = createFileRoute('/project/$ref/branches')({
+ component: BranchesShell,
+})
+
+// BranchLayout sibling-file shell. Each leaf renders its own per-page
+// PageLayout (different titles + primary/secondary actions); we keep
+// that wrapping in the leaf route files via the page-side
+// `*PageWrapper` exports rather than trying to share PageLayout in the
+// shell — the shell deliberately stops at BranchLayout.
+function BranchesShell() {
+ return (
+
+
+
+ )
+}
diff --git a/apps/studio/routes/project/$ref/branches/index.tsx b/apps/studio/routes/project/$ref/branches/index.tsx
new file mode 100644
index 00000000000..810d1eaa299
--- /dev/null
+++ b/apps/studio/routes/project/$ref/branches/index.tsx
@@ -0,0 +1,15 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+import BranchesPage, { BranchesPageWrapper } from '@/pages/project/[ref]/branches/index'
+
+export const Route = createFileRoute('/project/$ref/branches/')({
+ component: BranchesIndexRoute,
+})
+
+function BranchesIndexRoute() {
+ return (
+
+
+
+ )
+}
diff --git a/apps/studio/routes/project/$ref/branches/merge-requests.tsx b/apps/studio/routes/project/$ref/branches/merge-requests.tsx
new file mode 100644
index 00000000000..e5367b92a22
--- /dev/null
+++ b/apps/studio/routes/project/$ref/branches/merge-requests.tsx
@@ -0,0 +1,17 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+import MergeRequestsPage, {
+ MergeRequestsPageWrapper,
+} from '@/pages/project/[ref]/branches/merge-requests'
+
+export const Route = createFileRoute('/project/$ref/branches/merge-requests')({
+ component: MergeRequestsRoute,
+})
+
+function MergeRequestsRoute() {
+ return (
+
+
+
+ )
+}