Skip to content

[404] design the not-found page with an auth-aware shell - #174

Merged
BhuvanArn merged 11 commits into
stagingfrom
404-design-not-found-page
Jul 15, 2026
Merged

[404] design the not-found page with an auth-aware shell#174
BhuvanArn merged 11 commits into
stagingfrom
404-design-not-found-page

Conversation

@BhuvanArn

Copy link
Copy Markdown
Owner

What

The 404 page was an unstyled 13-line stub (<p>Not found!</p> + a "Go home" link). This gives it a designed page, shows the sidebar only when the visitor is authenticated, and offers a way out appropriate to their auth state.

Primary Secondary
Authed Go home → /applications Go back (router.history.back())
Anonymous Go home → / Log in → /login

Authed users get Go back because a dead link hit from inside the app is best escaped backwards. Anonymous users get Log in instead — someone anonymous on a deep link is usually an expired session, and Go back would bounce them onto the same dead page.

Why the root had to change

__root.tsx picked its shell from a hardcoded pathname allowlist. An unmatched URL (/zzz) is in neither PUBLIC_SHELL_PATHS nor PUBLIC_NAV_PATHS, so it fell through to the app-shell branch — the sidebar rendered around the 404 for logged-out visitors. Styling the page alone could not fix this.

The root now detects the not-found state from router match state (status === 'notFound' || globalNotFound, verified against @tanstack/router-core Matches.d.ts:49,72) and picks the shell itself. The new branch runs before the existing pathname checks; all three existing branches are untouched, so every currently-matching route behaves exactly as before.

Auth is read via useAuthStatus() (a real query), not useAuth(): 404 routes fire no route guard, so the context reports logged-out even for a signed-in user on a hard reload, which would flash the wrong shell. While the query is in flight the anonymous variant renders — no spinner; anonymous is the common case and lands correct immediately, and authed users usually skip the tick (staleTime: 60s, warm from in-app navigation).

main.tsx's defaultNotFoundComponent is now () => null: the root's branch renders <NotFoundPage /> instead of <Outlet />, so the router's substitution path (Outlet.js:225) is never mounted. Exactly one 404 renders, always with its shell.

Verified in a real browser

Not just tests — driven end-to-end against a live backend:

  • Anonymous: no sidebar, logo, Go home → /, Log in → /login
  • Authed: sidebar renders, Go home → /applications, and Go back clicked from a dead URL returned to /notes
  • Dark mode: works with zero extra code — every semantic token flips on its own
  • No console errors once auth resolves

Two bugs only the browser caught, both fixed here:

  • Duplicate <h1><Logo> already owns the page's h1; the heading is now h2. The original test queried by name without a level, so it passed. It now asserts the level, plus a test that exactly one h1 exists.
  • Content sat high, not centeredh-full resolved to nothing against a min-h-screen parent, so justify-center centered inside 525px of an 875px viewport.

Notes

  • Uses text-body-m, not text-body-md — the latter is undefined in tailwind.css (only .text-body-m at :366) and silently renders at browser-default size. EmptyState currently uses the broken class — pre-existing, out of scope, worth a follow-up.
  • The org-view accent flip (:root[data-view='organization'], blue → green) never reaches a 404: measured data-view as null there, because NavigationContext.tsx:104-108 sets it from navigation state and removes it on unmount. The button is always the default blue.
  • No new dependencies. No route-tree changes (-not-found.tsx keeps its - prefix).

Test plan

  • npx vitest run src/routes/-not-found.spec.tsx src/routes/-__root.spec.tsx — 11/11 pass
  • npm run lint:check — 0 warnings, 0 errors (web + server)
  • npx tsc -b — exit 0
  • prettier --check on all changed files — clean
  • Manual: anonymous + authed × light + dark, Go back clicked live

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
talk-up-ai-dev Ready Ready Preview, Comment Jul 15, 2026 4:11pm

@railway-app
railway-app Bot temporarily deployed to talk-up-ai / TalkUp.AI-pr-174 July 15, 2026 07:22 Destroyed
@railway-app

railway-app Bot commented Jul 15, 2026

Copy link
Copy Markdown

🚅 Deployed to the TalkUp.AI-pr-174 environment in talk-up-ai

Service Status Web Updated (UTC)
Backend ✅ Success (View Logs) Jul 15, 2026 at 4:11 pm

@BhuvanArn BhuvanArn self-assigned this Jul 15, 2026
@railway-app
railway-app Bot temporarily deployed to talk-up-ai / TalkUp.AI-pr-174 July 15, 2026 16:10 Destroyed
@BhuvanArn
BhuvanArn marked this pull request as ready for review July 15, 2026 20:03
Copilot AI review requested due to automatic review settings July 15, 2026 20:03

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 was unable to review this pull request because the user who requested the review has reached their quota limit.

@BhuvanArn
BhuvanArn merged commit dcaae5b into staging Jul 15, 2026
18 checks passed
@BhuvanArn
BhuvanArn deleted the 404-design-not-found-page branch July 15, 2026 22:52
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.

3 participants