Skip to content

Plugin admin page declared with path "/" never gets the sidebar active state (hits the official forms plugin) #2988

Description

@marks-zyz

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

  1. Any admin with a plugin that declares an admin page with path: "/" (the shipped forms plugin does).
  2. Click the item in the sidebar under Plugins.
  3. 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 "/".

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions