Add interactive misrouted-message 404 page#24
Conversation
|
Warning Review limit reached
Next review available in: 11 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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.
| {current.messages.map((message, index) => ( | ||
| <span key={message} className={`${s.packet} ${s[`packet${index + 1}`]}`}> | ||
| {message} | ||
| </span> | ||
| ))} |
There was a problem hiding this comment.
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.
| {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)}> |
There was a problem hiding this comment.
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.
| <button type="button" onClick={() => setAttempt((attempt + 1) % attempts.length)}> | |
| <button type="button" onClick={() => setAttempt((prev) => (prev + 1) % attempts.length)}> |
|
Preview deployed!
This is a Cloudflare Workers preview version of this PR's build. |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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>
| import { LostMessages } from './not-found/LostMessages'; | ||
| import s from './not-found/not-found.module.css'; | ||
|
|
||
| export const metadata: Metadata = { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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>
| 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}`]}`}> |
There was a problem hiding this comment.
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>
What changed
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 buildnpm test(82 tests passed)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.
Written for commit 7768cc6. Summary will update on new commits.