Compare commits
2 Commits
7bcf784d06
...
v0.52.3
| Author | SHA1 | Date | |
|---|---|---|---|
| afa8d26378 | |||
| 948ee88160 |
@@ -23,6 +23,19 @@ skip versions are the composition of each intervening adjacent
|
||||
release's steps in order — no A-to-B path is pre-computed beyond
|
||||
that.
|
||||
|
||||
## 0.52.3 — 2026-06-09
|
||||
|
||||
**Patch — unmatched routes render a 404 instead of a blank page or a
|
||||
silent redirect home (no operator action required).**
|
||||
|
||||
- **The top-level catch-all redirected any unknown path to `/`**, and the
|
||||
nested `/admin/*`, `/p/:projectId/*`, and `/docs/*` route groups had no
|
||||
catch-all (so an invalid subpath rendered an empty pane). Added a shared
|
||||
`NotFound` 404 page and wired it into all four route groups, so a bad or
|
||||
typo'd URL — including a stale `/admin/users/allowlist/graduation`-style
|
||||
path — now shows a clear "page not found" with a link back to the catalog.
|
||||
(Client-side 404 UI; the SPA still serves over HTTP 200, as before.)
|
||||
|
||||
## 0.52.2 — 2026-06-09
|
||||
|
||||
**Patch — admin sidebar nav links no longer accumulate URL segments (no
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "rfc-app-frontend",
|
||||
"private": true,
|
||||
"version": "0.52.2",
|
||||
"version": "0.52.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -2687,3 +2687,19 @@ select:focus-visible,
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 404 / not-found page (client-side, for unmatched routes) */
|
||||
.not-found {
|
||||
max-width: 32rem;
|
||||
margin: 4rem auto;
|
||||
padding: 0 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
.not-found-code {
|
||||
font-size: 3.5rem;
|
||||
line-height: 1;
|
||||
margin: 0 0 0.5rem;
|
||||
color: var(--c-gray-900, #111);
|
||||
}
|
||||
.not-found-message { color: var(--c-gray-700, #444); margin: 0 0 1.25rem; }
|
||||
.not-found-actions a { color: var(--c-gray-900, #111); text-decoration: underline; }
|
||||
|
||||
@@ -7,6 +7,7 @@ import { brandTitle } from './lib/brand'
|
||||
import { entryPath, proposalPath, DEFAULT_COLLECTION } from './lib/entryPaths'
|
||||
import { useDeployment } from './context/DeploymentProvider'
|
||||
import ProjectLayout from './components/ProjectLayout.jsx'
|
||||
import NotFound from './components/NotFound.jsx'
|
||||
import Directory from './components/Directory.jsx'
|
||||
import ProjectSwitcher from './components/ProjectSwitcher.jsx'
|
||||
import Catalog from './components/Catalog.jsx'
|
||||
@@ -381,13 +382,14 @@ export default function App() {
|
||||
<Route path="c/:collectionId/e/:slug" element={<RFCView viewer={viewer} />} />
|
||||
<Route path="c/:collectionId/e/:slug/pr/:prNumber" element={<PRView viewer={viewer} />} />
|
||||
<Route path="c/:collectionId/proposals/:prNumber" element={<ProposalView viewer={viewer} onChange={() => setCatalogVersion(v => v + 1)} />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</ProjectLayout>
|
||||
} />
|
||||
{/* Any other path (incl. the retired bare-slug corpus URLs that
|
||||
somehow reach the SPA) lands on the deployment landing. */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
{/* Any unmatched path is a real 404 — surface it rather than
|
||||
silently bouncing home, so a bad/typo'd URL is visible. */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</div>
|
||||
{(proposeOpen || proposeParam != null) && viewer && (
|
||||
@@ -507,6 +509,7 @@ function DocsWithSidebar({ viewer }) {
|
||||
client-side redirect to the first configured spec. */}
|
||||
<Route path="specs" element={<DocsSpecsIndex />} />
|
||||
<Route path="specs/:name" element={<DocsSpec />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</main>
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from '../api.js'
|
||||
import { EVENTS, track } from '../lib/analytics.js'
|
||||
import { entryPath, useProjectId } from '../lib/entryPaths'
|
||||
import NotFound from './NotFound.jsx'
|
||||
|
||||
// v0.17.0 — roadmap item #16. The max length the backend enforces
|
||||
// (Pydantic body bound + `invites.CUSTOM_MESSAGE_MAX_LENGTH`); kept
|
||||
@@ -82,6 +83,7 @@ export default function Admin({ viewer }) {
|
||||
{isSiteOwner && <Route path="retired" element={<RetiredTab />} />}
|
||||
<Route path="audit" element={<AuditTab />} />
|
||||
<Route path="permissions" element={<PermissionsTab />} />
|
||||
<Route path="*" element={<NotFound message="That admin page doesn't exist." />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Client-side 404 for routes that don't match. An SPA serves index.html
|
||||
// for every non-API path (HTTP 200), so an unknown URL would otherwise
|
||||
// render blank — or, worse, silently bounce home. This surfaces a clear
|
||||
// "page not found" instead. Used by the top-level router and by the
|
||||
// nested /admin, /p/:projectId, and /docs route groups so an invalid
|
||||
// subpath (e.g. /admin/users/allowlist/graduation) lands here too.
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
export default function NotFound({ message }) {
|
||||
return (
|
||||
<div className="not-found" role="alert">
|
||||
<h1 className="not-found-code">404</h1>
|
||||
<p className="not-found-message">
|
||||
{message || "We couldn't find that page."}
|
||||
</p>
|
||||
<p className="not-found-actions">
|
||||
<Link to="/">Return to the catalog</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user