Description
LinkMenuItem in shell/menus/LinkMenuItem.tsx renders every variant as a plain anchor: Paragon's Hyperlink with destination, and NavLink, NavDropdown.Item and Dropdown.Item with href. When the URL comes from getUrlByRouteRole and an installed app provides the role, it is an in-site path such as /account, so the link reloads the whole shell instead of navigating within it. The user menu's Account and Profile entries (ProfileLinkMenuItem delegates to LinkMenuItem), the Help button and any header link an app registers through LinkMenuItem are affected, as is shell/Logo.tsx, which links to the home role through a bare Hyperlink. With frontend-app-account converted to a frontend-base app (openedx/frontend-app-account#1470), clicking Account in the header is the first place a learner hits this.
The shell already knows how to make this distinction. shell/header/anonymous-menu/utils.ts has getLinkProps(url), which returns { as: Link, to } for an in-site path and { href } otherwise, and the Login and Register buttons have used it since #305; runtime/routing/authenticatedLoader.ts and the course bar's isClientRoute make the same test. Only LinkMenuItem and Logo predate it. A plain href is also wrong for a site deployed under a basename, since react-router's Link prepends it and an anchor does not.
Proposed solution
Apply the same treatment to LinkMenuItem and Logo: resolve the URL as today, then spread getLinkProps (or the helper #317 asks for, which would also cover param substitution and the trailing splat) into whichever Paragon element the variant renders, all of which accept as. The navLink variant already derives active from useLocation, so it only gains from staying in the router. Cover it with tests along the lines of LoginButton.test.tsx: an internal role renders a router link, an external route or url prop renders an anchor. Once header links navigate in place, #318's missing pending indicator becomes more visible for lazily loaded apps like Account.
LLM usage notice
Built with assistance from Claude.
Description
LinkMenuIteminshell/menus/LinkMenuItem.tsxrenders every variant as a plain anchor: Paragon'sHyperlinkwithdestination, andNavLink,NavDropdown.ItemandDropdown.Itemwithhref. When the URL comes fromgetUrlByRouteRoleand an installed app provides the role, it is an in-site path such as/account, so the link reloads the whole shell instead of navigating within it. The user menu's Account and Profile entries (ProfileLinkMenuItemdelegates toLinkMenuItem), the Help button and any header link an app registers throughLinkMenuItemare affected, as isshell/Logo.tsx, which links to the home role through a bareHyperlink. With frontend-app-account converted to a frontend-base app (openedx/frontend-app-account#1470), clicking Account in the header is the first place a learner hits this.The shell already knows how to make this distinction.
shell/header/anonymous-menu/utils.tshasgetLinkProps(url), which returns{ as: Link, to }for an in-site path and{ href }otherwise, and the Login and Register buttons have used it since #305;runtime/routing/authenticatedLoader.tsand the course bar'sisClientRoutemake the same test. OnlyLinkMenuItemandLogopredate it. A plainhrefis also wrong for a site deployed under abasename, since react-router'sLinkprepends it and an anchor does not.Proposed solution
Apply the same treatment to
LinkMenuItemandLogo: resolve the URL as today, then spreadgetLinkProps(or the helper #317 asks for, which would also cover param substitution and the trailing splat) into whichever Paragon element the variant renders, all of which acceptas. ThenavLinkvariant already derivesactivefromuseLocation, so it only gains from staying in the router. Cover it with tests along the lines ofLoginButton.test.tsx: an internal role renders a router link, an external route orurlprop renders an anchor. Once header links navigate in place, #318's missing pending indicator becomes more visible for lazily loaded apps like Account.LLM usage notice
Built with assistance from Claude.