Skip to content

fix(routing): not-found page for unknown URLs; menu editor warns about links that match no route (#8103) - #8108

Merged
renemadsen merged 2 commits into
stablefrom
fix/8103-not-found-page-menu-warning
Oct 4, 2026
Merged

renemadsen merged 2 commits into
stablefrom
fix/8103-not-found-page-menu-warning

Conversation

@renemadsen

Copy link
Copy Markdown
Member

Summary

  • A URL that matches no route now shows a "Page not found" page (da: "Siden blev ikke fundet") instead of silently redirecting to My eForms. The page shows the requested URL, which stays in the address bar, and has a button to the start page.
  • The menu editor warns when an internal link matches no route:
    • a hint under the link field in the custom-link and edit dialogs;
    • a warning icon on menu rows whose link matches no route.
  • The edit dialog offers "Restore default link" for an entry created from a plugin template whose link was changed.

Root cause

app.routing.ts ended with {path: '**', redirectTo: ''}. A menu entry with a wrong link therefore opened My eForms, with no hint that anything was wrong:

  • a plugin link saved without the /plugins prefix;
  • a link to a removed plugin route.

The menu editor accepted such links without checking them.

Details

  • ** now renders NotFoundComponent inside FullLayoutComponent, behind IsAuthGuard (logged-out visitors still go to login). It must stay the last app route: AppModule also imports PluginsModule eagerly, so plugin routes are appended at root level after it, where nothing can reach them. This is why a plugin link without /plugins lands on the not-found page.
  • linkMatchesRoute (common/helpers/route-match.helper.ts) walks the router config in the router's first-match order and stops at **. Lazy modules are not loaded for the check:
    • A matched lazy route vouches for the paths under it.
    • The plugin routes (plugins.routing.ts) and My eForms' sub-modules are supplied up front. The sub-modules come from the new side-effect-free eforms-route-paths.ts, which eforms.routing.ts now also uses.
    • A menu template's default link always passes.
    • External links are not checked.
  • "Restore default link" only looks in plugin templates. A core item dragged in but not yet saved carries RelatedTemplateItemId = 1, not its own id.
  • Five new translation keys, translated into all 26 languages.
  • Not done: the issue's optional part 3 (record who saved the menu, single-transaction save) needs backend work and is left for a separate change.

Tests

  • Playwright Tests/c/navigation-menu.unknown-route.spec.ts:
    • An unknown URL (with a query string) and a plugin link without /plugins both show the not-found page and keep the URL; the start-page button works.
    • The custom-link dialog warns for /no-such-page, does not warn for /advanced/sites, and drops the warning when the link is made external.
  • Jest:
    • route-match.helper.spec.ts: route-walk cases, including My eForms sub-pages and plugin prefixes.
    • navigation-menu-item-edit.component.spec.ts: the warning, an external link, a template default link, and "Restore default link".

Not verified

  • Playwright and Jest have not run yet; tests run only in CI.
  • I checked the behaviour by hand against a locally served build (ng build --configuration development passes) with the API proxied to a local app.
  • tsc and check-button-conventions.js are clean.

Refs #8103

🤖 Generated with Claude Code

…ns about links that match no route (#8103)

A URL that matched no route was redirected to My eForms by the catch-all
`{path: '**', redirectTo: ''}`, so a menu entry with a wrong link (a plugin
link without the /plugins prefix, a removed plugin route) silently opened
My eForms, and the menu editor accepted such links without a word.

- The catch-all now renders a "Page not found" page inside the full layout,
  showing the requested URL (which stays in the address bar) and a button to
  the start page. Logged-out visitors are still sent to login.
- The menu editor checks internal links against the app's routes: a warning
  under the link field in the custom-link and edit dialogs, and a warning
  icon on menu rows whose link matches no route. Lazy modules are not
  loaded for this; a matched lazy route vouches for the paths under it, with
  the plugin routes and My eForms' sub-modules supplied up front. A menu
  template's default link always passes.
- The edit dialog offers "Restore default link" for an entry created from a
  plugin template whose link was changed.
- Five new translation keys in every language.

Refs #8103

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 3, 2026 05:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 1 High severity · 2 Medium severity · 1 Low severity

Open (4)
What changed in this PR

Adds a proper “not found” experience for unknown URLs and improves the navigation menu editor by warning when internal links don’t match any reachable route, reducing silent misnavigation (e.g., bad plugin links without /plugins).

Changes:

  • Replace the wildcard redirect with a guarded NotFoundComponent so unknown URLs render a “Page not found” page while keeping the original URL.
  • Introduce route-walk helpers/services (linkMatchesRoute, MenuLinkRouteService) and wire them into menu editor UI (warnings + “Restore default link”).
  • Add i18n keys and automated coverage (Jest helper/unit tests + Playwright e2e).
File Description
eform-client/​src/​assets/​i18n/​bgBG.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​csCZ.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​da.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​deDE.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​elGR.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​enUS.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​esES.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​etET.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​fiFI.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​frFR.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​hrHR.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​huHU.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​isIS.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​itIT.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​ltLT.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​lvLV.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​nlNL.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​noNO.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​plPL.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​ptBR.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​ptPT.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​roRO.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​skSK.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​slSL.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​svSE.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​assets/​i18n/​ukUA.ts Add new translation keys for not-found + menu link warning/restore
eform-client/​src/​app/​modules/​eforms/​eforms.routing.ts Use shared constants for eForms child route paths
eform-client/​src/​app/​modules/​eforms/​eforms-route-paths.ts New side-effect-free constants for eForms child route paths
eform-client/​src/​app/​modules/​advanced/​modules/​navigation-menu/​menu-link-route.service.ts New service to validate menu links vs router config and resolve plugin template defaults
eform-client/​src/​app/​modules/​advanced/​modules/​navigation-menu/​components/​navigation-menu-page/​navigation-menu-page.component.ts Pass templates to edit dialog; expose linkHasNoRoute() helper
eform-client/​src/​app/​modules/​advanced/​modules/​navigation-menu/​components/​navigation-menu-page/​navigation-menu-page.component.html Show warning icon for items whose internal link matches no route
eform-client/​src/​app/​modules/​advanced/​modules/​navigation-menu/​components/​menu-item/​navigation-menu-item/​navigation-menu-item.component.ts Add linkHasNoRoute input for child rows
eform-client/​src/​app/​modules/​advanced/​modules/​navigation-menu/​components/​menu-item/​navigation-menu-item/​navigation-menu-item.component.html Render warning icon for child menu items
eform-client/​src/​app/​modules/​advanced/​modules/​navigation-menu/​components/​menu-item/​navigation-menu-item-edit/​navigation-menu-item-edit.component.ts Add no-route warning + “Restore default link” logic using route/template service
eform-client/​src/​app/​modules/​advanced/​modules/​navigation-menu/​components/​menu-item/​navigation-menu-item-edit/​navigation-menu-item-edit.component.html Display link warning hint and restore-default button
eform-client/​src/​app/​modules/​advanced/​modules/​navigation-menu/​components/​menu-item/​navigation-menu-item-edit/​navigation-menu-item-edit.component.spec.ts Add Jest coverage for link warnings and restore-default behavior
eform-client/​src/​app/​modules/​advanced/​modules/​navigation-menu/​components/​menu-custom/​navigation-menu-custom-link/​navigation-menu-custom-link.component.ts Add no-route warning getter on custom-link dialog
eform-client/​src/​app/​modules/​advanced/​modules/​navigation-menu/​components/​menu-custom/​navigation-menu-custom-link/​navigation-menu-custom-link.component.html Display link warning hint in custom-link dialog
eform-client/​src/​app/​components/​not-found/​not-found.component.ts New not-found component that keeps the requested URL visible
eform-client/​src/​app/​components/​not-found/​not-found.component.html New not-found page UI (shows URL + start-page button)
eform-client/​src/​app/​components/​index.ts Export NotFoundComponent
eform-client/​src/​app/​common/​helpers/​route-match.helper.ts New helper to match internal links against router config (with lazy-route handling)
eform-client/​src/​app/​common/​helpers/​route-match.helper.spec.ts Jest tests for route matching logic and edge cases
eform-client/​src/​app/​common/​helpers/​index.ts Export new route-match helper
eform-client/​src/​app/​app.routing.ts Replace wildcard redirect with guarded not-found route under FullLayoutComponent
eform-client/​src/​app/​app.module.ts Declare NotFoundComponent
eform-client/​playwright/​e2e/​Tests/​c/​navigation-menu.unknown-route.spec.ts Add Playwright e2e coverage for unknown URLs + menu editor warnings

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread eform-client/src/app/common/helpers/route-match.helper.ts
Comment thread eform-client/src/app/components/not-found/not-found.component.ts
…8103)

mat-icon is aria-hidden by default; the warning icon now has role="img",
aria-hidden="false" and the translated warning as its aria-label, so
screen reader users hear it too.

Refs #8103

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 3, 2026 08:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Missing router test providers break component tests, and plugin default-link metadata is lost after saving and reloading the menu.

Review effort: Balanced
Findings: 2 High severity

Open (2)
Resolved since last review (4)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Menu persistence loses plugin template relationship after reload

eform-client/​src/​app/​modules/​advanced/​modules/​navigation-menu/​components/​menu-item/​navigation-menu-item-edit/​navigation-menu-item-edit.component.ts:66

The default-link lookup stops working after the menu is saved and reloaded. The API rebuilds link items in SimpleLinkBehavior, where assigning MenuTemplateId is commented out, so GetCurrentNavigationMenu subsequently returns relatedTemplateItemId: null; this lookup then cannot identify the plugin template and the promised “Restore default link” action disappears. Preserve or reconstruct the plugin template relationship during menu persistence (without assigning the core template's placeholder id) and cover the save/reload case.

export class NavigationMenuCustomLinkComponent implements OnInit {
dialogRef = inject<MatDialogRef<NavigationMenuCustomLinkComponent>>(MatDialogRef);
availableSecurityGroups = inject(MAT_DIALOG_DATA) ?? [];
private menuLinkRouteService = inject(MenuLinkRouteService);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set aside: no provider is missing. Router is @Injectable({providedIn: 'root'}) in @angular/router 20, so TestBed builds one (with an empty route config) without provideRouter. The test-angular-unit check on this PR ran this spec and it passes (PASS …/navigation-menu-custom-link.component.spec.ts, 41/41 suites).

})
export class NavigationMenuItemEditComponent implements OnInit {
dialogRef = inject<MatDialogRef<NavigationMenuItemEditComponent>>(MatDialogRef);
private menuLinkRouteService = inject(MenuLinkRouteService);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set aside, same as on the custom-link spec: Router is root-provided in @angular/router 20, so the TestBed injects a Router with no routes — which is exactly what the #8103 link-check tests rely on ("The TestBed router has no routes"). test-angular-unit on this PR passes this spec, including the new assertions.

@renemadsen
renemadsen merged commit d19a852 into stable Oct 4, 2026
18 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants