Skip to content

Commit 6bb6657

Browse files
fix(studio): refresh sidebar metadata when switching packages
Make the sidebar package dropdown drive the URL instead of local state. The $package layout already syncs selectedPackage from the URL param, so the previous setSelectedPackage-only handler was being immediately reverted by that effect, which kept AppSidebar.loadMetadata from re-running. Now selecting a package navigates to /$newPackage; the URL->state effect updates selectedPackage, which invalidates loadMetadata's dependency and refreshes the left metadata list for the newly selected package. Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/3dce7059-3349-474c-b9c3-aa576806fc14 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent ab54c2d commit 6bb6657

2 files changed

Lines changed: 23 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
- Updated `content/docs/guides/packages.mdx` and `content/docs/concepts/packages.mdx` to reflect the actual **42 package** inventory and to include `service-package` and `service-tenant`
2828

2929
### Fixed
30+
- **Studio left metadata list not refreshing on package switch** — In `apps/studio/src/routes/$package.tsx`, the `AppSidebar` package-switcher's `onSelectPackage` handler only updated local `selectedPackage` state. A URL→state `useEffect` in the same layout then immediately reverted that state back to match the unchanged `$package` route param, so `AppSidebar.loadMetadata` (keyed on `selectedPackage`) never re-ran and the left metadata tree stayed stuck on the previous package. The dropdown now navigates to `/$newPackage`, making the URL the single source of truth; the URL→state effect then updates `selectedPackage` normally and the metadata list refreshes for the new package. (`apps/studio/src/routes/$package.tsx`)
3031
- **Cross-origin auth tokens stripped in `@objectstack/hono` adapter (follow-up to PR #1178)**`createHonoApp()` was not exposing `set-auth-token` via `Access-Control-Expose-Headers`, diverging from `plugin-hono-server`'s CORS wiring. On Vercel deployments (where all traffic flows through `createHonoApp()`), the browser stripped the header from every response, preventing the better-auth `bearer()` plugin from delivering rotated session tokens to cross-origin clients. Cross-origin sessions silently broke even after the wildcard fixes in #1177/#1178. The adapter now always includes `set-auth-token` in `exposeHeaders`, merged with any user-supplied values, mirroring the invariant established in commit `151dd19c`. (`packages/adapters/hono/src/index.ts`)
3132
- **CORS wildcard patterns in `@objectstack/hono` adapter (follow-up to PR #1177)**`createHonoApp()` was the third CORS code path that still treated wildcard origins (e.g. `https://*.objectui.org`) as literal strings when passing them to Hono's `cors()` middleware. Because `apps/server` routes all non-OPTIONS requests through this adapter on Vercel, the browser would see a successful preflight (handled by the Vercel short-circuit) followed by a POST/GET response with no `Access-Control-Allow-Origin` header, blocking every real request. The adapter now imports `hasWildcardPattern` / `createOriginMatcher` from `@objectstack/plugin-hono-server` and uses the same matcher-function branch as `plugin-hono-server`, so all three Hono-based CORS paths share a single source of truth. (`packages/adapters/hono/src/index.ts`)
3233
- **CORS wildcard patterns on Vercel deployments**`CORS_ORIGIN` values containing wildcard patterns (e.g. `https://*.objectui.org,https://*.objectstack.ai,http://localhost:*`) no longer cause browser CORS errors when `apps/server` is deployed to Vercel. The Vercel entrypoint's OPTIONS preflight short-circuit previously matched origins with a literal `Array.includes()`, treating `*` as a plain character and rejecting legitimate subdomains. It now shares the same pattern-matching logic as the Hono plugin's `cors()` middleware via new exports `createOriginMatcher` / `hasWildcardPattern` / `matchOriginPattern` / `normalizeOriginPatterns` from `@objectstack/plugin-hono-server`. (`apps/server/server/index.ts`, `packages/plugins/plugin-hono-server/src/pattern-matcher.ts`)

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

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

3-
import { createFileRoute, Outlet } from '@tanstack/react-router';
3+
import { createFileRoute, Outlet, useNavigate } from '@tanstack/react-router';
44
import { AppSidebar } from '../components/app-sidebar';
55
import { usePackages } from '../hooks/usePackages';
6-
import { useEffect } from 'react';
6+
import { useCallback, useEffect } from 'react';
7+
import type { InstalledPackage } from '@objectstack/spec/kernel';
78

89
/**
910
* Layout for every `/$package/*` route.
@@ -14,25 +15,42 @@ import { useEffect } from 'react';
1415
* object view, metadata view) provide accurate breadcrumbs without prop-
1516
* drilling. It also prevents the duplicated-shell bug that occurred when
1617
* both this layout and its children each rendered their own `AppSidebar`.
18+
*
19+
* The URL `$package` param is the single source of truth for the current
20+
* package. Selecting a new package in the sidebar dropdown navigates to
21+
* `/$newPackage`; the URL→state effect below then updates `selectedPackage`,
22+
* which in turn invalidates `AppSidebar.loadMetadata` (its dependency) and
23+
* causes the left metadata list to refresh for the newly selected package.
1724
*/
1825
function PackageLayoutComponent() {
1926
const { package: packageId } = Route.useParams();
2027
const { packages, selectedPackage, setSelectedPackage } = usePackages();
28+
const navigate = useNavigate();
2129

22-
// Update selected package when route param changes
30+
// Sync selection from the URL param (single source of truth).
2331
useEffect(() => {
2432
const pkg = packages.find(p => p.manifest?.id === packageId);
2533
if (pkg && pkg !== selectedPackage) {
2634
setSelectedPackage(pkg);
2735
}
2836
}, [packageId, packages, selectedPackage, setSelectedPackage]);
2937

38+
// Selecting a package in the sidebar dropdown must drive the URL;
39+
// otherwise the URL→state effect above would immediately revert the
40+
// local state back to the old package and the metadata list would
41+
// never refresh.
42+
const handleSelectPackage = useCallback((pkg: InstalledPackage) => {
43+
const nextId = pkg.manifest?.id;
44+
if (!nextId || nextId === packageId) return;
45+
navigate({ to: '/$package', params: { package: nextId } });
46+
}, [navigate, packageId]);
47+
3048
return (
3149
<>
3250
<AppSidebar
3351
packages={packages}
3452
selectedPackage={selectedPackage}
35-
onSelectPackage={setSelectedPackage}
53+
onSelectPackage={handleSelectPackage}
3654
/>
3755
<main className="flex min-w-0 flex-1 flex-col h-svh overflow-hidden bg-background">
3856
<Outlet />

0 commit comments

Comments
 (0)