-
Notifications
You must be signed in to change notification settings - Fork 1
Add interactive misrouted-message 404 page #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import type { Metadata } from 'next'; | ||
| import Link from 'next/link'; | ||
| import { ArrowLeft, BookOpen } from 'lucide-react'; | ||
|
|
||
| import { LogoIcon, LogoWordmark } from '../components/SiteNav'; | ||
| 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.', | ||
| robots: { | ||
| index: false, | ||
| follow: true, | ||
| }, | ||
| }; | ||
|
|
||
| export default function NotFound() { | ||
| return ( | ||
| <main className={s.page}> | ||
| <header className={s.nav}> | ||
| <Link href="/" className={s.brand} aria-label="Agent Relay home"> | ||
| <LogoIcon /> | ||
| <LogoWordmark /> | ||
| </Link> | ||
|
|
||
| <span className={s.routeCode}>ERR_ROUTE_NOT_FOUND</span> | ||
| </header> | ||
|
|
||
| <div className={s.layout}> | ||
| <section className={s.copy}> | ||
| <div className={s.errorCode} aria-hidden="true"> | ||
| 404 | ||
| </div> | ||
| <p className={s.kicker}>Delivery failed</p> | ||
| <h1>Message sent. Page never joined.</h1> | ||
| <p className={s.lede}>A few agents are looking for it. They are not doing a great job.</p> | ||
|
|
||
| <div className={s.actions}> | ||
| <Link href="/" className={s.primaryAction}> | ||
| <ArrowLeft aria-hidden="true" size={18} strokeWidth={1.8} /> | ||
| Back to safety | ||
| </Link> | ||
| <Link href="/docs" className={s.secondaryAction}> | ||
| <BookOpen aria-hidden="true" size={18} strokeWidth={1.8} /> | ||
| Read the docs | ||
| </Link> | ||
| </div> | ||
| </section> | ||
|
|
||
| <LostMessages /> | ||
| </div> | ||
|
|
||
| <footer className={s.footer}> | ||
| <span>Agent Relay</span> | ||
| <span>Undeliverable messages return to sender. Eventually.</span> | ||
| </footer> | ||
| </main> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,98 @@ | ||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import { useState } from 'react'; | ||||||||||||||||||||||
| import { RotateCcw } from 'lucide-react'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import s from './not-found.module.css'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const attempts = [ | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| status: 'No route to channel', | ||||||||||||||||||||||
| note: 'Builder sent /ship-it to a room that does not exist.', | ||||||||||||||||||||||
| messages: ['anyone here?', 'wrong channel', 'hello?'], | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| status: 'Recipient missing', | ||||||||||||||||||||||
| note: 'Reviewer asked 404 to approve the pull request.', | ||||||||||||||||||||||
| messages: ['please review', 'still there?', 'nudge'], | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| status: 'Context wandered off', | ||||||||||||||||||||||
| note: 'Scout found the page, then forgot where it put it.', | ||||||||||||||||||||||
| messages: ['found it!', 'wait', 'lost it'], | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| ] as const; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export function LostMessages() { | ||||||||||||||||||||||
| const [attempt, setAttempt] = useState(0); | ||||||||||||||||||||||
| const current = attempts[attempt]; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| <section className={s.network} aria-label="Failed message routing simulation"> | ||||||||||||||||||||||
| <div className={s.networkHeader}> | ||||||||||||||||||||||
| <div> | ||||||||||||||||||||||
| <span className={s.liveLabel}>Live misrouting</span> | ||||||||||||||||||||||
| <p>{current.status}</p> | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Prompt for AI agents |
||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| <span className={s.deliveryState}>undelivered</span> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <div className={s.stage} key={attempt}> | ||||||||||||||||||||||
| <div className={`${s.connection} ${s.connectionOne}`} /> | ||||||||||||||||||||||
| <div className={`${s.connection} ${s.connectionTwo}`} /> | ||||||||||||||||||||||
| <div className={`${s.connection} ${s.connectionThree}`} /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <AgentNode className={s.builder} initials="BU" name="Builder" status="sent" /> | ||||||||||||||||||||||
| <AgentNode className={s.scout} initials="SC" name="Scout" status="searching" /> | ||||||||||||||||||||||
| <AgentNode className={s.reviewer} initials="RV" name="Reviewer" status="confused" /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <div className={s.missingNode}> | ||||||||||||||||||||||
| <span>?</span> | ||||||||||||||||||||||
| <strong>missing-page</strong> | ||||||||||||||||||||||
| <small>never connected</small> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| {current.messages.map((message, index) => ( | ||||||||||||||||||||||
| <span key={message} className={`${s.packet} ${s[`packet${index + 1}`]}`}> | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Using the message string as a React Prompt for AI agents |
||||||||||||||||||||||
| {message} | ||||||||||||||||||||||
| </span> | ||||||||||||||||||||||
| ))} | ||||||||||||||||||||||
|
Comment on lines
+55
to
+59
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using the message string as a React
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <div className={s.deadLetter}> | ||||||||||||||||||||||
| <span>DEAD LETTER</span> | ||||||||||||||||||||||
| <strong>404</strong> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <div className={s.networkFooter}> | ||||||||||||||||||||||
| <p>{current.note}</p> | ||||||||||||||||||||||
| <button type="button" onClick={() => setAttempt((attempt + 1) % attempts.length)}> | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When updating state based on the previous state, it is safer to use the functional state updater form (
Suggested change
|
||||||||||||||||||||||
| <RotateCcw aria-hidden="true" size={15} strokeWidth={1.8} /> | ||||||||||||||||||||||
| Misroute again | ||||||||||||||||||||||
| </button> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| </section> | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| function AgentNode({ | ||||||||||||||||||||||
| className, | ||||||||||||||||||||||
| initials, | ||||||||||||||||||||||
| name, | ||||||||||||||||||||||
| status, | ||||||||||||||||||||||
| }: { | ||||||||||||||||||||||
| className: string; | ||||||||||||||||||||||
| initials: string; | ||||||||||||||||||||||
| name: string; | ||||||||||||||||||||||
| status: string; | ||||||||||||||||||||||
| }) { | ||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| <div className={`${s.agent} ${className}`}> | ||||||||||||||||||||||
| <span className={s.avatar}>{initials}</span> | ||||||||||||||||||||||
| <span> | ||||||||||||||||||||||
| <strong>{name}</strong> | ||||||||||||||||||||||
| <small>{status}</small> | ||||||||||||||||||||||
| </span> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
There was a problem hiding this comment.
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 forglobal-not-found.js. This leaves the page with inherited metadata (and potentially indexable when no status-basednoindextag is injected), so the SEO behavior described by the PR is not guaranteed; using a supportedglobal-not-foundmetadata path would make it reliable.Prompt for AI agents