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
13 changes: 13 additions & 0 deletions .changeset/nav-sys-objects-canonical-route-3739.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@object-ui/app-shell': patch
---

Point the `sys-objects` navigation entries at the canonical metadata-admin route instead of the `system/metadata/object` alias, removing a redirect hop from each click (objectui#3739).

`AppSidebar.systemFallbackNavigation`, `UnifiedSidebar.homeNavigation` and `console/home/QuickActions` all spelled this target `/apps/setup/system/metadata/object`. That is not a page: `apps/console`'s host fragment declares `system/metadata/:metadataType` with `MetadataRedirect` as its element, which immediately navigates on to `/apps/setup/metadata/object` — the engine's real route (`metadata/:type`, `MetadataResourceListPage`). Every click therefore paid a redundant hop plus a re-render to reach a destination the navigation could name directly. All three now name it.

This is the same defect objectui#3660 fixed for `sys-datasources`, declared on the line immediately below `sys-objects` in both sidebar arrays. It was missed there because the two entries reached their aliases through different route tables — `sys-datasources` through app-shell's own `component/metadata/resource` alias, `sys-objects` through the host's `system/metadata/:type` rewrite.

The landing page is unchanged, byte for byte: the new URL is exactly what the alias hop was already computing (`object` percent-encodes to itself, and no producer carried a query or hash). Only the intermediate hop is gone. Of the three producers, the two sidebars are live; `QuickActions` has no JSX call site today, so its change is a guard against the dead link returning with the component.

The alias routes stay declared and untouched: bookmarks and external links still arrive on them and are still forwarded.
9 changes: 8 additions & 1 deletion packages/app-shell/src/console/home/QuickActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@ export function QuickActions() {
label: t('home.quickActions.manageObjects', { defaultValue: 'Manage Objects' }),
description: t('home.quickActions.manageObjectsDesc', { defaultValue: 'Configure data models' }),
icon: Database,
href: '/apps/setup/system/metadata/object',
// #3739 — the metadata-admin engine's CANONICAL route, not the legacy
// `…/system/metadata/object` alias this card used to carry. That alias is
// not a page: `apps/console`'s host fragment serves it with
// `MetadataRedirect`, a bare `<Navigate>` onto the URL below, so every
// click paid a redundant hop plus a re-render. Same defect and same
// remedy as #3660's `sys-datasources` entry in both sidebars. The alias
// routes stay for bookmarks and external links.
href: '/apps/setup/metadata/object',
iconBg: 'bg-gradient-to-br from-violet-500/15 to-purple-500/10 ring-violet-500/20',
iconText: 'text-violet-600 dark:text-violet-400',
hoverBorder: 'hover:border-violet-500/40',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@
* Configured" empty state's own URL on a zero-app deployment. The card's
* sibling ("Manage Objects") already spelled `/apps/setup/system/...`, which is
* what made this one the odd entry out.
*
* ## The "Manage Objects" card (objectui#3739)
*
* That sibling's `/apps/setup/system/...` prefix is what made it the anchor for
* #3611 — and it was the wrong URL for a different reason, unnoticed at the
* time. `…/system/metadata/object` is not a page: `apps/console`'s host fragment
* serves it with `MetadataRedirect`, a bare `<Navigate>` onto
* `/apps/setup/metadata/object`, so the card bought a redundant hop plus a
* re-render. It is the third of the three producers #3739 re-points; the other
* two are the `sys-objects` entries in both sidebars
* (`layout/__tests__/systemNavSettingsTarget.test.tsx`).
*
* So the third case below is REWRITTEN, not extended: it used to assert this
* card as the untouched consistency anchor, and that premise is what #3739
* falsified. It now pins the canonical target — the same discipline as the
* sidebar suites, which replace the old shape rather than keeping the bug and
* the fix side by side. Whether that URL resolves in one hop is a property of
* the URL rather than of this producer, and is measured in
* `layout/__tests__/systemNavObjectsHop.test.tsx`.
*/

import '@testing-library/jest-dom/vitest';
Expand Down Expand Up @@ -60,6 +79,9 @@ function renderQuickActions() {

const SYSTEM_HUB = '/apps/setup/system';

/** Where the metadata-admin engine really serves the object list (objectui#3739). */
const OBJECTS_TARGET = '/apps/setup/metadata/object';

describe('QuickActions system-settings card (objectui#3611, dormant)', () => {
it('DORMANCY PRECONDITION: nothing renders this component, so the fix is a guard, not a user-visible change', async () => {
// Recorded as an assertion rather than prose so it goes red the day the
Expand Down Expand Up @@ -99,12 +121,23 @@ describe('QuickActions system-settings card (objectui#3611, dormant)', () => {
expect(screen.getByTestId('landing')).toHaveTextContent(SYSTEM_HUB);
});

it('REGRESSION: the sibling card that was already hub-scoped is unchanged', async () => {
it('the Manage Objects card targets the canonical metadata route, not the system alias (objectui#3739)', async () => {
// Replaces #3611's "the sibling is unchanged" anchor. That assertion was
// true of the URL and wrong about it: `…/system/metadata/object` is an alias
// the host only forwards, so pinning it froze the extra hop in place.
const user = userEvent.setup();
renderQuickActions();

await user.click(screen.getByTestId('quick-action-manage-objects'));

expect(screen.getByTestId('landing')).toHaveTextContent(`${SYSTEM_HUB}/metadata/object`);
expect(screen.getByTestId('landing')).toHaveTextContent(OBJECTS_TARGET);
// `toHaveTextContent` matches on substring, and neither spelling contains
// the other (`/apps/setup/system/metadata/object` has the extra segment in
// the MIDDLE), so the assertion above already separates them. This second
// one adds nothing to the logic and is kept for the diff: a revert then
// fails naming the alias, instead of reporting two similar-looking paths.
expect(screen.getByTestId('landing')).not.toHaveTextContent(
`${SYSTEM_HUB}/metadata/object`,
);
});
});
19 changes: 17 additions & 2 deletions packages/app-shell/src/layout/AppSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,12 @@ export function AppSidebar({ activeAppName, onAppChange }: { activeAppName: stri
// `/apps/setup` renders `AppContent`'s "No Apps Configured" empty state (its
// `isSystemRoute` guard needs a `/system` segment), so the cluster's head entry
// was a dead link in the one situation the cluster exists for. Every sibling
// below already spells `/apps/setup/system/...`.
// below already spelled `/apps/setup/system/...` — the two metadata-admin
// entries excepted, and only since: they name the engine's canonical
// `/apps/setup/metadata/:type` routes (#3660, #3739), because for THOSE two
// the `system/...` spelling is a redirect rather than a page. Consistency of
// prefix is not the invariant here; naming the route that actually renders
// is.
const systemFallbackNavigation: NavigationItem[] = React.useMemo(() => {
const items: NavigationItem[] = [
{ id: 'sys-settings', label: t('layout.systemNav.systemSettings', { defaultValue: 'System Settings' }), type: 'url' as const, url: '/apps/setup/system', icon: 'settings' },
Expand All @@ -326,7 +331,17 @@ export function AppSidebar({ activeAppName, onAppChange }: { activeAppName: stri
items.push({ id: 'sys-marketplace', label: t('layout.systemNav.appMarketplace', { defaultValue: 'App Marketplace' }), type: 'url' as const, url: '/apps/setup/system/marketplace', icon: 'store' });
}
items.push(
{ id: 'sys-objects', label: t('layout.systemNav.objectManager', { defaultValue: 'Object Manager' }), type: 'url' as const, url: '/apps/setup/system/metadata/object', icon: 'database' },
// #3739 — `sys-objects` names the metadata-admin engine's CANONICAL route
// `/apps/setup/metadata/object`, not the legacy
// `…/system/metadata/object` alias it used to carry. That alias is not a
// page either: `apps/console`'s host fragment declares it as
// `MetadataRedirect`, which `<Navigate>`s onto exactly the URL spelled
// here, so every click paid a redundant hop plus a re-render — the same
// defect #3660 fixed one line below, on the entry immediately after this
// one, and the same remedy. The alias routes stay declared in the host
// (bookmarks and external links still arrive on them) — we simply stop
// aiming our own navigation at them.
{ id: 'sys-objects', label: t('layout.systemNav.objectManager', { defaultValue: 'Object Manager' }), type: 'url' as const, url: '/apps/setup/metadata/object', icon: 'database' },
// #3660 — `sys-datasources` names the metadata-admin engine's CANONICAL
// route `/apps/setup/metadata/datasource`, not the legacy
// `…/component/metadata/resource?type=datasource` alias it used to carry.
Expand Down
14 changes: 12 additions & 2 deletions packages/app-shell/src/layout/UnifiedSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -327,12 +327,22 @@ export function UnifiedSidebar({ activeAppName }: UnifiedSidebarProps) {
// `/system` form resolves in BOTH branches (`extraRoutesNoApp` with no
// active app, `extraRoutes` once one exists), so an app-bearing deployment
// now reaches the hub here instead of whatever app `/apps/setup` fell back
// to. Every sibling below already spells `/apps/setup/system/...`.
// to. Every sibling below already spelled `/apps/setup/system/...` — the
// two metadata-admin entries excepted, and only since: they name the
// engine's canonical `/apps/setup/metadata/:type` routes (#3660, #3739),
// because for THOSE two the `system/...` spelling is a redirect rather
// than a page.
const adminItems: NavigationItem[] = [
{ id: 'sys-settings', label: t('layout.systemNav.systemSettings', { defaultValue: 'System Settings' }), type: 'url' as const, url: '/apps/setup/system', icon: 'settings' },
{ id: 'sys-apps', label: t('layout.systemNav.applications', { defaultValue: 'Applications' }), type: 'url' as const, url: '/apps/setup/system/apps', icon: 'layout-grid' },
{ id: 'sys-marketplace', label: t('layout.systemNav.appMarketplace', { defaultValue: 'App Marketplace' }), type: 'url' as const, url: '/apps/setup/system/marketplace', icon: 'store' },
{ id: 'sys-objects', label: t('layout.systemNav.objectManager', { defaultValue: 'Object Manager' }), type: 'url' as const, url: '/apps/setup/system/metadata/object', icon: 'database' },
// #3739 — canonical `…/metadata/object`, not the legacy
// `…/system/metadata/object` alias. See the twin entry in
// `AppSidebar.systemFallbackNavigation` for the full note: the alias is
// served by `apps/console`'s `MetadataRedirect`, a bare `<Navigate>`
// onto this very URL, so pointing here removes a hop without moving the
// landing page. The alias routes themselves are untouched.
{ id: 'sys-objects', label: t('layout.systemNav.objectManager', { defaultValue: 'Object Manager' }), type: 'url' as const, url: '/apps/setup/metadata/object', icon: 'database' },
// #3660 — canonical `…/metadata/datasource`, not the legacy
// `…/component/metadata/resource?type=datasource` alias. See the twin
// entry in `AppSidebar.systemFallbackNavigation` for the full note: the
Expand Down
Loading
Loading