What happens
A plugin admin page declared with path: "/" never gets the active state in the sidebar. The link works and the page renders; only the highlight is missing, so the sidebar shows no selected item while you are on that page.
This hits the official @emdash-cms/plugin-forms: its first page is declared as { path: "/", label: "Forms" }, and "Forms" stays unhighlighted while /plugins/emdash-forms is open. Its second page (/submissions) highlights correctly.
Repro
- Any admin with a plugin that declares an admin page with
path: "/" (the shipped forms plugin does).
- Click the item in the sidebar under Plugins.
- The page loads, the URL becomes
/_emdash/admin/plugins/<id>, and no sidebar item is marked active.
Measured on emdash 0.36.0 / @emdash-cms/admin 0.36.0, in astro dev, by reading data-active on the sidebar anchors:
/_emdash/admin/plugins/emdash-forms/submissions -> data-active="true"
/_emdash/admin/plugins/emdash-forms -> no data-active
Cause
packages/admin/src/components/Sidebar.tsx:364 builds the target by concatenation:
pluginItems.push({
to: `/plugins/${pluginId}${page.path}`,
...
With page.path === "/" that yields /plugins/<id>/, with a trailing slash. The router renders and navigates to the normalized path (/plugins/<id>, no trailing slash), but isItemActive compares the raw to against the current path (Sidebar.tsx:226):
return path === "/"
? currentPath === "/"
: currentPath === path || currentPath.startsWith(`${path}/`);
"/plugins/emdash-forms" === "/plugins/emdash-forms/" is false, and startsWith("/plugins/emdash-forms//") is false too, so the item can never match.
Suggested fix
Normalize the trailing slash on both sides in isItemActive, keeping the special case for the admin root:
const TRAILING_SLASHES = /\/+$/;
function stripTrailingSlash(value: string): string {
return value.length > 1 ? value.replace(TRAILING_SLASHES, "") : value;
}
export function isItemActive(itemPath: string, currentPath: string): boolean {
const queryIndex = itemPath.indexOf("?");
const raw = queryIndex === -1 ? itemPath : itemPath.slice(0, queryIndex);
const path = stripTrailingSlash(raw);
const current = stripTrailingSlash(currentPath);
return path === "/"
? current === "/"
: current === path || current.startsWith(`${path}/`);
}
That keeps every existing case (the function is exported and covered by packages/admin/tests/components/Sidebar.test.tsx) and fixes any item whose to ends in a slash, not just plugin pages.
Happy to send this as a PR with a test for the path: "/" case if you want it in this shape.
Workaround for plugin authors
Declare the page with a real segment (path: "/status") instead of "/".
What happens
A plugin admin page declared with
path: "/"never gets the active state in the sidebar. The link works and the page renders; only the highlight is missing, so the sidebar shows no selected item while you are on that page.This hits the official
@emdash-cms/plugin-forms: its first page is declared as{ path: "/", label: "Forms" }, and "Forms" stays unhighlighted while/plugins/emdash-formsis open. Its second page (/submissions) highlights correctly.Repro
path: "/"(the shipped forms plugin does)./_emdash/admin/plugins/<id>, and no sidebar item is marked active.Measured on emdash 0.36.0 / @emdash-cms/admin 0.36.0, in
astro dev, by readingdata-activeon the sidebar anchors:Cause
packages/admin/src/components/Sidebar.tsx:364builds the target by concatenation:With
page.path === "/"that yields/plugins/<id>/, with a trailing slash. The router renders and navigates to the normalized path (/plugins/<id>, no trailing slash), butisItemActivecompares the rawtoagainst the current path (Sidebar.tsx:226):"/plugins/emdash-forms" === "/plugins/emdash-forms/"is false, andstartsWith("/plugins/emdash-forms//")is false too, so the item can never match.Suggested fix
Normalize the trailing slash on both sides in
isItemActive, keeping the special case for the admin root:That keeps every existing case (the function is exported and covered by
packages/admin/tests/components/Sidebar.test.tsx) and fixes any item whosetoends in a slash, not just plugin pages.Happy to send this as a PR with a test for the
path: "/"case if you want it in this shape.Workaround for plugin authors
Declare the page with a real segment (
path: "/status") instead of"/".