Skip to content

feat(ui): add generic not-found page for unrecognized urls - #1340

Open
MRashad26 wants to merge 9 commits into
mainfrom
feat/LFXV2-2010-not-found-page
Open

feat(ui): add generic not-found page for unrecognized urls#1340
MRashad26 wants to merge 9 commits into
mainfrom
feat/LFXV2-2010-not-found-page

Conversation

@MRashad26

@MRashad26 MRashad26 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds NotFoundComponent with a branded 404 card (amber warning-triangle icon, "Page not found" heading, generic copy about mistyped URLs or stale bookmarks)
  • /not-found is a named public route classified as auth: 'public' in the Express middleware (same pattern as auth-error and invite/error) — accessible without login
  • The Angular ** wildcard does redirectTo: '/not-found' so authenticated users who mistype a URL land on the branded page with a clean URL
  • app.routes.server.ts sets status: 404 on the not-found server route so SSR emits the correct HTTP status (not a soft 200)
  • "Go Back" guards against empty history with isPlatformBrowser + window.history.length > 1; falls back to router.navigate(['/']) for direct links and stale bookmarks

Refs: LFXV2-2010

Scope note: The primary use case is authenticated users who mistype a URL — they see the branded 404 immediately. Anonymous users with a wrong URL go through login first (consistent with every other protected route in this auth-first app) and land on /not-found after authenticating. This is a deliberate architectural constraint: Angular's authGuard returns true on the server (trusting Express middleware), so making the catch-all optional would render protected SSR components without auth context.

Test plan

  • Visit any invalid URL (e.g. /foundation/tlf/groups, /foo/bar) while logged in — should show the not-found card at /not-found with HTTP 404
  • Visit /not-found directly while not logged in — should show the branded card without a login redirect
  • Click "Go Back" from a page you navigated to — should return to the previous page
  • Click "Go Back" from a direct link (no prior history) — should navigate to / (dashboard fallback)
  • Click "Go to Dashboard" — should navigate to /
  • Verify existing routes (/, /foundation/overview, /foundations/tlf/groups) still work as expected

LFXV2-2010 — adds a branded 404 experience via a new `NotFoundComponent`
wired to the Angular `**` wildcard route. The page renders inside a card
with a map-location-dot icon, a generic "Page not found" heading, and two
CTAs: "Go to Dashboard" (routerLink to /) and "Go Back" (Location.back()).

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
Copilot AI balanced review requested due to automatic review settings August 6, 2026 14:54
@MRashad26
MRashad26 requested a review from a team as a code owner August 6, 2026 14:54
@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Routing and public-auth classification changes are localized; the catch-all is last and mirrors existing error-page patterns with limited blast radius.

Overview
Introduces a branded app-wide 404 for bad or stale URLs instead of leaving users on ambiguous responses or soft 200s.

A new NotFoundComponent at /not-found shows a card with recovery actions (dashboard, go back with browser-history fallback to /). Express auth middleware treats /not-found as public (like auth-error), and SSR sets status: 404 on that route so crawlers and monitors see a real not-found.

Routing adds a terminal **/not-found redirect so authenticated mistyped paths land on the branded page. lfx-header gains optional showMyMeetings (off on the 404 page) so the meetings shortcut does not appear on that minimal shell.

Reviewed by Cursor Bugbot for commit 42eead4. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d38da346-c94e-47d6-9400-eea1207c6e80

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a branded Angular fallback page for unmatched URLs.

Changes:

  • Adds a generic 404 component with dashboard and back actions.
  • Registers a final wildcard route.
  • Supports lazy loading and anonymous client-side navigation.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
not-found.component.ts Implements back navigation.
not-found.component.html Defines the branded 404 UI and actions.
app.routes.ts Registers the wildcard fallback route.
Suppressed comments (1)

apps/lfx-one/src/app/app.routes.ts:491

  • This renders the 404 UI but still returns HTTP 200 for a direct SSR request: app.routes.server.ts leaves its ** entry at the default status. That creates a soft 404 for crawlers, caches, and monitoring. Use the established /docs/not-found approach (a dedicated route with status: 404, reached by redirecting the client wildcard) or an SSR response-status mechanism, and verify the response status in E2E.
    path: '**',
    loadComponent: () => import('./modules/not-found/not-found.component').then((m) => m.NotFoundComponent),

Comment thread apps/lfx-one/src/app/app.routes.ts Outdated
Comment thread apps/lfx-one/src/app/app.routes.ts
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

🚀 Deployment Status

Your branch has been deployed to: https://ui-pr-1340.dev.v2.cluster.linuxfound.info

Deployment Details:

  • Environment: Development
  • Namespace: ui-pr-1340
  • ArgoCD App: ui-pr-1340

The deployment will be automatically removed when this PR is closed.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

🧹 Deployment Removed

The deployment for PR #1340 has been removed.

LFXV2-2010

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
Copilot AI review requested due to automatic review settings August 6, 2026 15:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (2)

apps/lfx-one/src/app/app.routes.ts:488

  • Moving this Angular route outside authGuard does not make direct requests anonymous. auth.middleware.ts classifies every unmatched SSR path with the required-auth / fallback and redirects an anonymous GET to login before Angular can match this wildcard. As a result, the advertised anonymous invalid-URL flow never reaches this page. Add server-side handling that permits only genuinely unrecognized URLs to render the 404, without making the required-auth catch-all public.
  // Generic 404 — catches all unrecognized URLs and renders a branded not-found page. Must be
  // last so it never shadows real routes. Outside the auth guard so anonymous users see it too.

apps/lfx-one/src/app/app.routes.ts:491

  • This wildcard selects the component but does not set the SSR response status. The matching ** entry in app.routes.server.ts has no status, so a direct invalid URL is returned as HTTP 200 (a soft 404); the existing docs/not-found route demonstrates the required status: 404 behavior. Set 404 only when this Angular wildcard is matched—do not add 404 to the server-wide ** entry, which also renders valid routes.
    path: '**',
    loadComponent: () => import('./modules/not-found/not-found.component').then((m) => m.NotFoundComponent),

@audigregorie audigregorie left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review Summary

Clean, well-scoped PR that faithfully mirrors the established AuthErrorComponent pattern — the ** wildcard is correctly placed last and outside the auth guard with a clear "why" comment. One Major worth addressing: Location.back() has no fallback for the page's own described scenario (deep-linked/stale-bookmark 404s have no in-app history, so "Go Back" navigates away from LFX).

Outside the diff

  • Optional E2E coverage. The component ships data-testid hooks (not-found-card, not-found-title, not-found-dashboard-button, not-found-back-button) ready for the dual-architecture E2E suite. A small *.spec.ts asserting the wildcard route renders the card and the dashboard button navigates to / would lock this in — not blocking for a 3-file UI PR, but low-cost and high-value.

What's done well

  • ** wildcard is last and outside the auth guard, with a clear intent comment — won't shadow real routes and anonymous users see it.
  • Component mirrors the AuthErrorComponent shell (lfx-header + lfx-card, inject() style, data-testid convention) — easy to maintain.
  • Tailwind scale classes (gray, amber) instead of hard-coded hex, per styling.md.

Comment thread apps/lfx-one/src/app/modules/not-found/not-found.component.ts Outdated
Comment thread apps/lfx-one/src/app/modules/not-found/not-found.component.html Outdated
audigregorie
audigregorie previously approved these changes Aug 6, 2026
Address review comments from @copilot-pull-request-reviewer, @cursor:

- app.routes.ts: replace direct ** wildcard component load with a named
  /not-found route + ** redirectTo '/not-found', so the 404 page has a
  stable public URL regardless of the originating path
- auth.middleware.ts: add /not-found as auth: 'public' (regex-anchored)
  so unauthenticated users can reach the branded 404 without being
  redirected to login — same pattern as auth-error and invite/error
- app.routes.server.ts: add /not-found server route with status: 404 so
  SSR returns the correct HTTP status instead of a soft-404 (HTTP 200),
  fixing crawler, cache, and monitoring behaviour

Resolves 2 review threads.

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
Copilot AI review requested due to automatic review settings August 6, 2026 15:27
@MRashad26

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 0ed3f2a

Changes Made

  • app.routes.ts: Replaced direct ** wildcard component load with a named /not-found route + ** → redirectTo '/not-found'. The 404 page now lives at a stable, public URL rather than rendering at whatever mistyped path the user arrived on. (per @copilot-pull-request-reviewer, @cursor)
  • auth.middleware.ts: Added /not-found as auth: 'public' (regex-anchored /^\/not-found$/) before the '/'\ catch-all, following the same pattern as auth-errorandinvite/error`. Anonymous users can now reach the branded 404 directly without hitting a login redirect. (per @copilot-pull-request-reviewer, @cursor)
  • app.routes.server.ts: Added a dedicated not-found server route with status: 404 so SSR emits the correct HTTP status — fixes soft-404 behaviour for crawlers, caches, and monitoring. (per suppressed Copilot comment)

Threads Resolved

2 of 2 unresolved threads addressed in this iteration.

Address review comments from @audigregorie:

- not-found.component.ts: guard Location.back() with isPlatformBrowser +
  window.history.length > 1; fall back to router.navigate(['/']) when there
  is no in-app history (direct link / stale bookmark scenario described in
  the page copy) (per @audigregorie — Major)
- not-found.component.html: add aria-hidden="true" to the decorative
  warning icon so screen readers skip it (per @audigregorie — Minor)

Resolves 2 review threads.

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
@MRashad26

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 408bea8

Changes Made

  • not-found.component.ts: Guarded Location.back() with isPlatformBrowser + window.history.length > 1; falls back to router.navigate(['/']) for direct links and stale bookmarks that arrive with no in-app history (per @audigregorie — Major)
  • not-found.component.html: Added aria-hidden="true" to the decorative warning icon (per @audigregorie — Minor)

Threads Resolved

2 of 2 unresolved threads addressed in this iteration.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

apps/lfx-one/src/server/middleware/auth.middleware.ts:92

  • This pattern excludes /not-found/, so an anonymous request with a trailing slash falls through to the required-auth / rule and is sent to login instead of the public 404. The neighboring auth-error and public-profile regexes explicitly accept an optional trailing slash; apply the same exact-but-slash-tolerant match here.
  { pattern: /^\/not-found$/, type: 'ssr', auth: 'public' },

Comment thread apps/lfx-one/src/app/app.routes.ts
Copilot AI review requested due to automatic review settings August 6, 2026 15:33
@MRashad26

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

No Change Needed

  • app.routes.ts:499: @copilot-pull-request-reviewer correctly identified that anonymous users on unrecognized URLs still hit login before reaching /not-found. This is a deliberate constraint of the auth-first architecture — Angular's authGuard returns true on the server (trusts Express middleware exclusively), so making the catch-all optional would render protected SSR components without auth context. The PR description has been updated to accurately scope the behavior to authenticated users as the primary audience.

Threads Resolved

1 of 1 unresolved threads addressed in this iteration.

audigregorie
audigregorie previously approved these changes Aug 6, 2026
…anonymous-user behavior

Co-authored-by: MRashad26 <81649923+MRashad26@users.noreply.github.com>

Copilot AI commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Clean, well-scoped PR that faithfully mirrors the established AuthErrorComponent pattern — the ** wildcard is correctly placed last and outside the auth guard with a clea...

Scoped to the second option from the reviewer: the ** wildcard works correctly for authenticated users (Angular SSR evaluates the redirect to /not-found before any auth gate matters). For anonymous users the Express auth middleware classifies unrecognized paths via the '/' catch-all as required, redirects to /login?returnTo=<original-path>, and after authentication the returnTo URL is honoured — Angular's wildcard then redirects to /not-found. Implementing pre-auth server-side classification of "unknown vs valid-but-protected" paths would require duplicating the full Angular route table in Express, which is fragile and out of scope here.

Updated the route comments in app.routes.ts to accurately describe this two-step flow for anonymous users instead of claiming they reach /not-found directly without a session. The /not-found route itself remains auth: 'public' (commit fix(ui): correct comments on not-found routes to accurately describe anonymous-user behavior).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (2)

apps/lfx-one/src/app/app.routes.ts:499

  • Anonymous requests to arbitrary unknown URLs still never execute this redirect. authMiddleware runs before Angular SSR (server.ts:304), classifies the original path via the required / catch-all (auth.middleware.ts:94-95), and sends unauthenticated SSR GETs to login (auth.middleware.ts:361-374). The new public rule only helps requests already targeting /not-found, so /foo/bar still shows a login prompt rather than the branded 404 promised by this route. Unknown SSR paths need to be identified/rendered before the required-auth decision while keeping recognized protected routes fail-closed.
  },
  // Generic catch-all — redirects unrecognized URLs to /not-found so authenticated users get a

apps/lfx-one/src/server/middleware/auth.middleware.ts:92

  • This new public-auth boundary is not covered by auth.middleware.spec.ts, even though that suite regression-tests analogous anchored public rules and their fail-closed variants. Add cases proving anonymous /not-found is allowed and a prefix such as /not-found-admin remains protected; otherwise a future pattern/order change can silently expose a protected route or make the 404 inaccessible.
  { pattern: /^\/not-found$/, type: 'ssr', auth: 'public' },

Copilot AI review requested due to automatic review settings August 6, 2026 15:38
audigregorie
audigregorie previously approved these changes Aug 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (3)

apps/lfx-one/src/app/modules/not-found/not-found.component.ts:23

  • history.length counts the tab's entire session history, including entries from other sites, so it cannot distinguish an in-app navigation from a direct link or stale bookmark opened in a reused tab. In that common direct-entry case this branch calls back() and sends the user away from LFX instead of using the promised dashboard fallback. Base this decision on Angular navigation state or an explicitly tracked same-app previous URL, falling back to / when no LFX navigation exists.
    if (isPlatformBrowser(this.platformId) && window.history.length > 1) {
      this.location.back();

apps/lfx-one/src/server/middleware/auth.middleware.ts:92

  • This new public-route exception changes the authentication boundary but has no corresponding middleware test. Add coverage proving anonymous /not-found is allowed and a lookalike such as /not-found-admin still falls through to required auth, matching the existing anchored-regex tests in auth.middleware.spec.ts. This prevents a future regex/order edit from silently widening or breaking the public surface.
  { pattern: /^\/not-found$/, type: 'ssr', auth: 'public' },

apps/lfx-one/src/app/app.routes.server.ts:21

  • The new 404 status mapping and wildcard redirect are not covered by an automated browser test, even though the analogous docs/not-found status contract is asserted in e2e/docs/public-access.spec.ts:101-107. Add Playwright coverage that requests an unknown authenticated URL, verifies the final /not-found UI/status behavior, and directly requests /not-found anonymously. Without it, a route-order or SSR redirect regression can silently restore a soft 200 or login loop.
    path: 'not-found',
    renderMode: RenderMode.Server,
    status: 404,

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
The My Meetings shortcut link has no place on standalone pages
(not-found, auth-error, invite, groups) that use lfx-header
outside the main app layout. The avatar menu and logo link are
sufficient for navigation on these pages.

LFXV2-2010

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
Copilot AI review requested due to automatic review settings August 6, 2026 15:54
MRashad26 and others added 2 commits August 6, 2026 18:55
Add showMyMeetings input (default true) to HeaderComponent so callers
can opt out. Set it to false on the not-found page — the CTA has no
purpose on a 404 error page. All other standalone pages (groups,
auth-error, invite) retain the default and keep the link.

LFXV2-2010

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

apps/lfx-one/src/app/shared/components/header/header.component.ts:33

  • Consider typing the input signal explicitly (e.g., boolean) so template bindings can’t accidentally pass non-boolean values (like strings) without being caught by type checking.
  public readonly showMyMeetings = input(true);

apps/lfx-one/src/app/app.routes.ts:506

  • Using an absolute redirectTo (leading '/') can be problematic with non-root base-hrefs and is less consistent with typical Angular route redirects. Prefer redirectTo: 'not-found' for a root-level wildcard redirect.
  {
    path: '**',
    redirectTo: '/not-found',
  },

Comment on lines +11 to +15
@Component({
selector: 'lfx-not-found',
imports: [HeaderComponent, CardComponent, ButtonComponent, RouterLink],
templateUrl: './not-found.component.html',
})
Copilot AI review requested due to automatic review settings August 6, 2026 16:12

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 42eead4. Configure here.

} else {
void this.router.navigate(['/']);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Go Back leaves site

Medium Severity

goBack() treats window.history.length > 1 as in-app history, but that count includes prior external sites. For the common case where someone opens a broken LFX link from email, Slack, or another site, location.back() still leaves LFX instead of using the dashboard fallback the page copy implies.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 42eead4. Configure here.

// Generic not-found page — public so anonymous users land on a branded 404 instead of a login
// prompt when redirected here from an unrecognized URL. Anchored regex prevents startsWith
// semantics from matching /not-found-admin or similar future paths.
{ pattern: /^\/not-found$/, type: 'ssr', auth: 'public' },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Trailing slash auth gap

Low Severity

The public /not-found auth pattern is anchored as /^\/not-found$/ with no optional trailing slash, unlike the sibling /^\/auth-error\/?$/ pattern. A request to /not-found/ therefore falls through to the required-auth catch-all and sends anonymous users to login instead of the branded public 404.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 42eead4. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants