Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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`

### Fixed
- **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`)
- **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`)
- **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`)
- **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`)
Expand Down
26 changes: 22 additions & 4 deletions apps/studio/src/routes/$package.tsx
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

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

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

// Update selected package when route param changes
// Sync selection from the URL param (single source of truth).
useEffect(() => {
const pkg = packages.find(p => p.manifest?.id === packageId);
if (pkg && pkg !== selectedPackage) {
setSelectedPackage(pkg);
}
}, [packageId, packages, selectedPackage, setSelectedPackage]);

// Selecting a package in the sidebar dropdown must drive the URL;
// otherwise the URL→state effect above would immediately revert the
// local state back to the old package and the metadata list would
// never refresh.
const handleSelectPackage = useCallback((pkg: InstalledPackage) => {
const nextId = pkg.manifest?.id;
if (!nextId || nextId === packageId) return;
navigate({ to: '/$package', params: { package: nextId } });
}, [navigate, packageId]);

return (
<>
<AppSidebar
packages={packages}
selectedPackage={selectedPackage}
onSelectPackage={setSelectedPackage}
onSelectPackage={handleSelectPackage}
/>
<main className="flex min-w-0 flex-1 flex-col h-svh overflow-hidden bg-background">
<Outlet />
Expand Down