Skip to content

Add interactive misrouted-message 404 page#24

Merged
willwashburn merged 1 commit into
mainfrom
codex/misdirected-404
Jul 21, 2026
Merged

Add interactive misrouted-message 404 page#24
willwashburn merged 1 commit into
mainfrom
codex/misdirected-404

Conversation

@willwashburn

@willwashburn willwashburn commented Jul 15, 2026

Copy link
Copy Markdown
Member

What changed

  • add a custom App Router 404 page for Agent Relay
  • visualize agents sending messages toward a missing recipient
  • add an interactive reroute control with several failure scenarios
  • support responsive layouts, keyboard focus, and reduced motion

Why

The site previously fell back to the framework's generic not-found experience. This gives missing routes a memorable, brand-consistent page while preserving clear paths back home and into the docs.

Impact

Visitors who reach an invalid URL now see a responsive error page with useful recovery actions. Search engines are instructed not to index the error page.

Validation

  • npm run build
  • npm test (82 tests passed)
  • desktop layout checked at 1440x900
  • mobile layout checked at 390x844
  • reroute interaction and horizontal overflow checked in-browser

Summary by cubic

Adds a custom, interactive 404 page that visualizes misrouted messages and offers clear recovery links. Replaces the generic not-found view and prevents the page from being indexed.

  • New Features
    • Interactive misrouting scene with three failure scenarios and a “Misroute again” control.
    • Accessible: keyboard focus states, ARIA labels, and reduced-motion support.
    • Responsive layout for desktop and mobile with safe overflow handling.
    • Robots set to noindex; clear actions to Home and Docs.

Written for commit 7768cc6. Summary will update on new commits.

Review in cubic

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@willwashburn, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 11 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b723c5f7-72f2-4deb-869a-093d8ae5c181

📥 Commits

Reviewing files that changed from the base of the PR and between b12639b and 7768cc6.

📒 Files selected for processing (3)
  • web/app/not-found.tsx
  • web/app/not-found/LostMessages.tsx
  • web/app/not-found/not-found.module.css
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/misdirected-404

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@willwashburn
willwashburn marked this pull request as ready for review July 15, 2026 11:03

@gemini-code-assist gemini-code-assist Bot 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

This pull request introduces a highly stylized and interactive 404 'Not Found' page for the Agent Relay application, consisting of a main layout, a CSS module with custom animations, and a client-side component simulating message misrouting. The feedback recommends improving React key usage in the simulation list to avoid duplicate key warnings, and using a functional state updater when cycling through simulation attempts to prevent stale closure bugs.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +55 to +59
{current.messages.map((message, index) => (
<span key={message} className={`${s.packet} ${s[`packet${index + 1}`]}`}>
{message}
</span>
))}

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.

medium

Using the message string as a React key can lead to duplicate key warnings and rendering issues if duplicate messages are ever added to the attempts array in the future. Since the attempts array is static and the parent .stage container already uses key={attempt} (which forces a complete remount when the attempt changes), using the array index as the key is safe and robust here.

Suggested change
{current.messages.map((message, index) => (
<span key={message} className={`${s.packet} ${s[`packet${index + 1}`]}`}>
{message}
</span>
))}
{current.messages.map((message, index) => (
<span key={index} className={`${s.packet} ${s['packet' + (index + 1)]}`}>
{message}
</span>
))}


<div className={s.networkFooter}>
<p>{current.note}</p>
<button type="button" onClick={() => setAttempt((attempt + 1) % attempts.length)}>

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.

medium

When updating state based on the previous state, it is safer to use the functional state updater form (prev => ...) to avoid potential stale closure bugs, especially if the button is clicked rapidly.

Suggested change
<button type="button" onClick={() => setAttempt((attempt + 1) % attempts.length)}>
<button type="button" onClick={() => setAttempt((prev) => (prev + 1) % attempts.length)}>

@github-actions

Copy link
Copy Markdown
Contributor

Preview deployed!

Environment URL
Web https://a0bbd7b5-agentrelay-web.agent-workforce.workers.dev

This is a Cloudflare Workers preview version of this PR's build.

@cubic-dev-ai cubic-dev-ai 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.

8 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="web/app/not-found/LostMessages.tsx">

<violation number="1" location="web/app/not-found/LostMessages.tsx:35">
P2: Activating “Misroute again” changes the routing scenario visually, but the updated status is not announced to screen readers because it is plain text rather than a live region. Mark the changing status as `aria-live="polite"` so the interactive state change is conveyed to assistive-technology users.</violation>

<violation number="2" location="web/app/not-found/LostMessages.tsx:56">
P3: Using the message string as a React `key` is fragile—if a future attempt ever contains duplicate message text, React will warn about duplicate keys and may render incorrectly. Since the parent `<div className={s.stage} key={attempt}>` already forces a full remount when the scenario changes, using the array `index` as key is safe and more robust here.</violation>
</file>

<file name="web/app/not-found.tsx">

<violation number="1" location="web/app/not-found.tsx:9">
P2: The custom 404 title, description, and robots settings are not applied from a regular `app/not-found.tsx`; Next.js documents metadata support here only for `global-not-found.js`. This leaves the page with inherited metadata (and potentially indexable when no status-based `noindex` tag is injected), so the SEO behavior described by the PR is not guaranteed; using a supported `global-not-found` metadata path would make it reliable.</violation>
</file>

<file name="web/app/not-found/not-found.module.css">

<violation number="1" location="web/app/not-found/not-found.module.css:142">
P3: Reduced-motion users still see the recovery controls move and the reroute icon rotate on interaction because this media query removes timing but not the transforms. The reduced-motion override should also set `transform: none` for the hover/active control and icon selectors.</violation>

<violation number="2" location="web/app/not-found/not-found.module.css:174">
P3: On touch devices, tapping a recovery control can leave it lifted or leave the reroute icon rotated because these hover rules are not limited to fine pointers. Scoping the page's hover rules to `(hover: hover) and (pointer: fine)` would prevent sticky touch hover states.</violation>

<violation number="3" location="web/app/not-found/not-found.module.css:459">
P3: The large `404` status is too faint to be reliably readable against the stage background. Increasing the alpha to a readable warm accent, or explicitly treating the duplicate number as decorative, would resolve the accessibility issue.</violation>

<violation number="4" location="web/app/not-found/not-found.module.css:516">
P3: The footer copy is below normal-text contrast on the lighter part of the page gradient, reducing readability for users with low vision. An opaque, higher-contrast muted color such as `#a8b8c8` would preserve the hierarchy while meeting contrast requirements.</violation>

<violation number="5" location="web/app/not-found/not-found.module.css:587">
P2: At small desktop/tablet widths, the animated packets travel past the centered `missingNode` instead of reaching it, making the core misrouting visualization incorrect. Stacking the layout earlier or deriving packet travel from the stage width would keep the animation aligned.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

<div className={s.networkHeader}>
<div>
<span className={s.liveLabel}>Live misrouting</span>
<p>{current.status}</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Activating “Misroute again” changes the routing scenario visually, but the updated status is not announced to screen readers because it is plain text rather than a live region. Mark the changing status as aria-live="polite" so the interactive state change is conveyed to assistive-technology users.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/not-found/LostMessages.tsx, line 35:

<comment>Activating “Misroute again” changes the routing scenario visually, but the updated status is not announced to screen readers because it is plain text rather than a live region. Mark the changing status as `aria-live="polite"` so the interactive state change is conveyed to assistive-technology users.</comment>

<file context>
@@ -0,0 +1,98 @@
+      <div className={s.networkHeader}>
+        <div>
+          <span className={s.liveLabel}>Live misrouting</span>
+          <p>{current.status}</p>
+        </div>
+        <span className={s.deliveryState}>undelivered</span>
</file context>

Comment thread web/app/not-found.tsx
import { LostMessages } from './not-found/LostMessages';
import s from './not-found/not-found.module.css';

export const metadata: Metadata = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The custom 404 title, description, and robots settings are not applied from a regular app/not-found.tsx; Next.js documents metadata support here only for global-not-found.js. This leaves the page with inherited metadata (and potentially indexable when no status-based noindex tag is injected), so the SEO behavior described by the PR is not guaranteed; using a supported global-not-found metadata path would make it reliable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/not-found.tsx, line 9:

<comment>The custom 404 title, description, and robots settings are not applied from a regular `app/not-found.tsx`; Next.js documents metadata support here only for `global-not-found.js`. This leaves the page with inherited metadata (and potentially indexable when no status-based `noindex` tag is injected), so the SEO behavior described by the PR is not guaranteed; using a supported `global-not-found` metadata path would make it reliable.</comment>

<file context>
@@ -0,0 +1,60 @@
+import { LostMessages } from './not-found/LostMessages';
+import s from './not-found/not-found.module.css';
+
+export const metadata: Metadata = {
+  title: 'Message not delivered',
+  description: 'This Agent Relay route could not be found.',
</file context>

}
}

@media (max-width: 1040px) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: At small desktop/tablet widths, the animated packets travel past the centered missingNode instead of reaching it, making the core misrouting visualization incorrect. Stacking the layout earlier or deriving packet travel from the stage width would keep the animation aligned.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/not-found/not-found.module.css, line 587:

<comment>At small desktop/tablet widths, the animated packets travel past the centered `missingNode` instead of reaching it, making the core misrouting visualization incorrect. Stacking the layout earlier or deriving packet travel from the stage width would keep the animation aligned.</comment>

<file context>
@@ -0,0 +1,750 @@
+  }
+}
+
+@media (max-width: 1040px) {
+  .page {
+    overflow: auto;
</file context>

color: #dbe9f4;
}

.primaryAction:hover,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: On touch devices, tapping a recovery control can leave it lifted or leave the reroute icon rotated because these hover rules are not limited to fine pointers. Scoping the page's hover rules to (hover: hover) and (pointer: fine) would prevent sticky touch hover states.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/not-found/not-found.module.css, line 174:

<comment>On touch devices, tapping a recovery control can leave it lifted or leave the reroute icon rotated because these hover rules are not limited to fine pointers. Scoping the page's hover rules to `(hover: hover) and (pointer: fine)` would prevent sticky touch hover states.</comment>

<file context>
@@ -0,0 +1,750 @@
+  color: #dbe9f4;
+}
+
+.primaryAction:hover,
+.secondaryAction:hover {
+  transform: translateY(-2px);
</file context>

margin-top: 34px;
}

.primaryAction,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Reduced-motion users still see the recovery controls move and the reroute icon rotate on interaction because this media query removes timing but not the transforms. The reduced-motion override should also set transform: none for the hover/active control and icon selectors.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/not-found/not-found.module.css, line 142:

<comment>Reduced-motion users still see the recovery controls move and the reroute icon rotate on interaction because this media query removes timing but not the transforms. The reduced-motion override should also set `transform: none` for the hover/active control and icon selectors.</comment>

<file context>
@@ -0,0 +1,750 @@
+  margin-top: 34px;
+}
+
+.primaryAction,
+.secondaryAction {
+  display: inline-flex;
</file context>

}

.deadLetter strong {
color: rgba(240, 179, 159, 0.28);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The large 404 status is too faint to be reliably readable against the stage background. Increasing the alpha to a readable warm accent, or explicitly treating the duplicate number as decorative, would resolve the accessibility issue.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/not-found/not-found.module.css, line 459:

<comment>The large `404` status is too faint to be reliably readable against the stage background. Increasing the alpha to a readable warm accent, or explicitly treating the duplicate number as decorative, would resolve the accessibility issue.</comment>

<file context>
@@ -0,0 +1,750 @@
+}
+
+.deadLetter strong {
+  color: rgba(240, 179, 159, 0.28);
+  font-family: var(--font-heading), sans-serif;
+  font-size: 3.2rem;
</file context>

.footer {
min-height: 58px;
border-top: 1px solid rgba(116, 184, 226, 0.13);
color: rgba(143, 163, 181, 0.72);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The footer copy is below normal-text contrast on the lighter part of the page gradient, reducing readability for users with low vision. An opaque, higher-contrast muted color such as #a8b8c8 would preserve the hierarchy while meeting contrast requirements.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/not-found/not-found.module.css, line 516:

<comment>The footer copy is below normal-text contrast on the lighter part of the page gradient, reducing readability for users with low vision. An opaque, higher-contrast muted color such as `#a8b8c8` would preserve the hierarchy while meeting contrast requirements.</comment>

<file context>
@@ -0,0 +1,750 @@
+.footer {
+  min-height: 58px;
+  border-top: 1px solid rgba(116, 184, 226, 0.13);
+  color: rgba(143, 163, 181, 0.72);
+  font-size: 0.65rem;
+}
</file context>
Suggested change
color: rgba(143, 163, 181, 0.72);
color: #a8b8c8;

</div>

{current.messages.map((message, index) => (
<span key={message} className={`${s.packet} ${s[`packet${index + 1}`]}`}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Using the message string as a React key is fragile—if a future attempt ever contains duplicate message text, React will warn about duplicate keys and may render incorrectly. Since the parent <div className={s.stage} key={attempt}> already forces a full remount when the scenario changes, using the array index as key is safe and more robust here.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/not-found/LostMessages.tsx, line 56:

<comment>Using the message string as a React `key` is fragile—if a future attempt ever contains duplicate message text, React will warn about duplicate keys and may render incorrectly. Since the parent `<div className={s.stage} key={attempt}>` already forces a full remount when the scenario changes, using the array `index` as key is safe and more robust here.</comment>

<file context>
@@ -0,0 +1,98 @@
+        </div>
+
+        {current.messages.map((message, index) => (
+          <span key={message} className={`${s.packet} ${s[`packet${index + 1}`]}`}>
+            {message}
+          </span>
</file context>

@willwashburn
willwashburn merged commit 6817409 into main Jul 21, 2026
3 checks passed
@willwashburn
willwashburn deleted the codex/misdirected-404 branch July 21, 2026 19:21
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.

1 participant