Skip to content

Commit 873fdc6

Browse files
fix(studio): stop duplicate sidebar rendering under /$package/* routes
Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/08c5c817-2897-4590-b642-3c2d7a4015ab Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent d3fe5e4 commit 873fdc6

File tree

6 files changed

+93
-48
lines changed

6 files changed

+93
-48
lines changed

apps/studio/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
### Patch Changes
66

7+
- **Fix duplicate sidebar rendering on `/$package/objects/:name` and `/$package/metadata/:type/:name`.** Both the parent `$package.tsx` layout and its children rendered their own `<AppSidebar>` + `<main>` + `<SiteHeader>` shell. With TanStack Router's flat file routing, children render inside the parent's `<Outlet>` — producing a visible copy of the left sidebar in the right content pane instead of the metadata detail.
8+
- `$package.tsx` is now a pure layout: `<AppSidebar>` + `<main>` wrapper + `<Outlet>`. No `SiteHeader`.
9+
- New `$package.index.tsx` leaf handles the exact `/$package` URL, rendering `<SiteHeader selectedView="overview">` + `<DeveloperOverview>`.
10+
- `$package.objects.$name.tsx` and `$package.metadata.$type.$name.tsx` simplified to render only their `<SiteHeader>` + `<PluginHost>`; shell is inherited from the parent layout.
711
- **Unified Studio mount path to `/_studio/` for all deployments.** The Vite
812
build default is now `base: '/_studio/'` (was `'./'`), baking the correct
913
absolute asset URLs and router basepath into every bundle. This removes the

apps/studio/src/routeTree.gen.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Route as PackagesRouteImport } from './routes/packages'
1313
import { Route as ApiConsoleRouteImport } from './routes/api-console'
1414
import { Route as PackageRouteImport } from './routes/$package'
1515
import { Route as IndexRouteImport } from './routes/index'
16+
import { Route as PackageIndexRouteImport } from './routes/$package.index'
1617
import { Route as PackageObjectsNameRouteImport } from './routes/$package.objects.$name'
1718
import { Route as PackageMetadataTypeNameRouteImport } from './routes/$package.metadata.$type.$name'
1819

@@ -36,6 +37,11 @@ const IndexRoute = IndexRouteImport.update({
3637
path: '/',
3738
getParentRoute: () => rootRouteImport,
3839
} as any)
40+
const PackageIndexRoute = PackageIndexRouteImport.update({
41+
id: '/',
42+
path: '/',
43+
getParentRoute: () => PackageRoute,
44+
} as any)
3945
const PackageObjectsNameRoute = PackageObjectsNameRouteImport.update({
4046
id: '/objects/$name',
4147
path: '/objects/$name',
@@ -52,14 +58,15 @@ export interface FileRoutesByFullPath {
5258
'/$package': typeof PackageRouteWithChildren
5359
'/api-console': typeof ApiConsoleRoute
5460
'/packages': typeof PackagesRoute
61+
'/$package/': typeof PackageIndexRoute
5562
'/$package/objects/$name': typeof PackageObjectsNameRoute
5663
'/$package/metadata/$type/$name': typeof PackageMetadataTypeNameRoute
5764
}
5865
export interface FileRoutesByTo {
5966
'/': typeof IndexRoute
60-
'/$package': typeof PackageRouteWithChildren
6167
'/api-console': typeof ApiConsoleRoute
6268
'/packages': typeof PackagesRoute
69+
'/$package': typeof PackageIndexRoute
6370
'/$package/objects/$name': typeof PackageObjectsNameRoute
6471
'/$package/metadata/$type/$name': typeof PackageMetadataTypeNameRoute
6572
}
@@ -69,6 +76,7 @@ export interface FileRoutesById {
6976
'/$package': typeof PackageRouteWithChildren
7077
'/api-console': typeof ApiConsoleRoute
7178
'/packages': typeof PackagesRoute
79+
'/$package/': typeof PackageIndexRoute
7280
'/$package/objects/$name': typeof PackageObjectsNameRoute
7381
'/$package/metadata/$type/$name': typeof PackageMetadataTypeNameRoute
7482
}
@@ -79,14 +87,15 @@ export interface FileRouteTypes {
7987
| '/$package'
8088
| '/api-console'
8189
| '/packages'
90+
| '/$package/'
8291
| '/$package/objects/$name'
8392
| '/$package/metadata/$type/$name'
8493
fileRoutesByTo: FileRoutesByTo
8594
to:
8695
| '/'
87-
| '/$package'
8896
| '/api-console'
8997
| '/packages'
98+
| '/$package'
9099
| '/$package/objects/$name'
91100
| '/$package/metadata/$type/$name'
92101
id:
@@ -95,6 +104,7 @@ export interface FileRouteTypes {
95104
| '/$package'
96105
| '/api-console'
97106
| '/packages'
107+
| '/$package/'
98108
| '/$package/objects/$name'
99109
| '/$package/metadata/$type/$name'
100110
fileRoutesById: FileRoutesById
@@ -136,6 +146,13 @@ declare module '@tanstack/react-router' {
136146
preLoaderRoute: typeof IndexRouteImport
137147
parentRoute: typeof rootRouteImport
138148
}
149+
'/$package/': {
150+
id: '/$package/'
151+
path: '/'
152+
fullPath: '/$package/'
153+
preLoaderRoute: typeof PackageIndexRouteImport
154+
parentRoute: typeof PackageRoute
155+
}
139156
'/$package/objects/$name': {
140157
id: '/$package/objects/$name'
141158
path: '/objects/$name'
@@ -154,11 +171,13 @@ declare module '@tanstack/react-router' {
154171
}
155172

156173
interface PackageRouteChildren {
174+
PackageIndexRoute: typeof PackageIndexRoute
157175
PackageObjectsNameRoute: typeof PackageObjectsNameRoute
158176
PackageMetadataTypeNameRoute: typeof PackageMetadataTypeNameRoute
159177
}
160178

161179
const PackageRouteChildren: PackageRouteChildren = {
180+
PackageIndexRoute: PackageIndexRoute,
162181
PackageObjectsNameRoute: PackageObjectsNameRoute,
163182
PackageMetadataTypeNameRoute: PackageMetadataTypeNameRoute,
164183
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { createFileRoute } from '@tanstack/react-router';
4+
import { SiteHeader } from '@/components/site-header';
5+
import { DeveloperOverview } from '../components/DeveloperOverview';
6+
import { usePackages } from '../hooks/usePackages';
7+
8+
/**
9+
* Leaf route for the exact `/$package` URL — the package overview page.
10+
*
11+
* Owns its own `SiteHeader`; the layout shell (sidebar + main wrapper) is
12+
* provided by the parent `$package.tsx` route.
13+
*/
14+
function PackageIndexComponent() {
15+
const { packages, selectedPackage } = usePackages();
16+
17+
return (
18+
<>
19+
<SiteHeader
20+
selectedView="overview"
21+
packageLabel={selectedPackage?.manifest?.name || selectedPackage?.manifest?.id}
22+
/>
23+
<div className="flex flex-1 flex-col overflow-hidden">
24+
<DeveloperOverview
25+
packages={packages}
26+
selectedPackage={selectedPackage}
27+
/>
28+
</div>
29+
</>
30+
);
31+
}
32+
33+
export const Route = createFileRoute('/$package/')({
34+
component: PackageIndexComponent,
35+
});

apps/studio/src/routes/$package.metadata.$type.$name.tsx

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,28 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
import { createFileRoute } from '@tanstack/react-router';
4-
import { AppSidebar } from '../components/app-sidebar';
54
import { SiteHeader } from '@/components/site-header';
65
import { PluginHost } from '../plugins';
76
import { usePackages } from '../hooks/usePackages';
87

98
function MetadataViewComponent() {
109
const { type, name } = Route.useParams();
11-
const { packages, selectedPackage, setSelectedPackage } = usePackages();
10+
const { selectedPackage } = usePackages();
1211

1312
return (
1413
<>
15-
<AppSidebar
16-
packages={packages}
17-
selectedPackage={selectedPackage}
18-
onSelectPackage={setSelectedPackage}
14+
<SiteHeader
15+
selectedMeta={{ type, name }}
16+
selectedView="metadata"
17+
packageLabel={selectedPackage?.manifest?.name || selectedPackage?.manifest?.id}
1918
/>
20-
<main className="flex min-w-0 flex-1 flex-col h-svh overflow-hidden bg-background">
21-
<SiteHeader
22-
selectedMeta={{ type, name }}
23-
selectedView="metadata"
24-
packageLabel={selectedPackage?.manifest?.name || selectedPackage?.manifest?.id}
19+
<div className="flex flex-1 flex-col overflow-hidden">
20+
<PluginHost
21+
metadataType={type}
22+
metadataName={name}
23+
packageId={selectedPackage?.manifest?.id}
2524
/>
26-
<div className="flex flex-1 flex-col overflow-hidden">
27-
<PluginHost
28-
metadataType={type}
29-
metadataName={name}
30-
packageId={selectedPackage?.manifest?.id}
31-
/>
32-
</div>
33-
</main>
25+
</div>
3426
</>
3527
);
3628
}

apps/studio/src/routes/$package.objects.$name.tsx

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,28 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
import { createFileRoute } from '@tanstack/react-router';
4-
import { AppSidebar } from '../components/app-sidebar';
54
import { SiteHeader } from '@/components/site-header';
65
import { PluginHost } from '../plugins';
76
import { usePackages } from '../hooks/usePackages';
87

98
function ObjectViewComponent() {
109
const { name } = Route.useParams();
11-
const { packages, selectedPackage, setSelectedPackage } = usePackages();
10+
const { selectedPackage } = usePackages();
1211

1312
return (
1413
<>
15-
<AppSidebar
16-
packages={packages}
17-
selectedPackage={selectedPackage}
18-
onSelectPackage={setSelectedPackage}
14+
<SiteHeader
15+
selectedObject={name}
16+
selectedView="object"
17+
packageLabel={selectedPackage?.manifest?.name || selectedPackage?.manifest?.id}
1918
/>
20-
<main className="flex min-w-0 flex-1 flex-col h-svh overflow-hidden bg-background">
21-
<SiteHeader
22-
selectedObject={name}
23-
selectedView="object"
24-
packageLabel={selectedPackage?.manifest?.name || selectedPackage?.manifest?.id}
19+
<div className="flex flex-1 flex-col overflow-hidden">
20+
<PluginHost
21+
metadataType="object"
22+
metadataName={name}
23+
packageId={selectedPackage?.manifest?.id}
2524
/>
26-
<div className="flex flex-1 flex-col overflow-hidden">
27-
<PluginHost
28-
metadataType="object"
29-
metadataName={name}
30-
packageId={selectedPackage?.manifest?.id}
31-
/>
32-
</div>
33-
</main>
25+
</div>
3426
</>
3527
);
3628
}

apps/studio/src/routes/$package.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,19 @@
22

33
import { createFileRoute, Outlet } from '@tanstack/react-router';
44
import { AppSidebar } from '../components/app-sidebar';
5-
import { SiteHeader } from '@/components/site-header';
65
import { usePackages } from '../hooks/usePackages';
76
import { useEffect } from 'react';
87

8+
/**
9+
* Layout for every `/$package/*` route.
10+
*
11+
* Renders the persistent left `AppSidebar` and the main content frame, and
12+
* delegates the `SiteHeader` + body rendering to the child leaf routes via
13+
* `<Outlet />`. Keeping the header in the children lets each leaf (index,
14+
* object view, metadata view) provide accurate breadcrumbs without prop-
15+
* drilling. It also prevents the duplicated-shell bug that occurred when
16+
* both this layout and its children each rendered their own `AppSidebar`.
17+
*/
918
function PackageLayoutComponent() {
1019
const { package: packageId } = Route.useParams();
1120
const { packages, selectedPackage, setSelectedPackage } = usePackages();
@@ -26,13 +35,7 @@ function PackageLayoutComponent() {
2635
onSelectPackage={setSelectedPackage}
2736
/>
2837
<main className="flex min-w-0 flex-1 flex-col h-svh overflow-hidden bg-background">
29-
<SiteHeader
30-
selectedView="overview"
31-
packageLabel={selectedPackage?.manifest?.name || selectedPackage?.manifest?.id}
32-
/>
33-
<div className="flex flex-1 flex-col overflow-hidden">
34-
<Outlet />
35-
</div>
38+
<Outlet />
3639
</main>
3740
</>
3841
);

0 commit comments

Comments
 (0)