From abc91f246e18bc8c13ca5149aa8a4e9e15a49c0e Mon Sep 17 00:00:00 2001 From: Rinjani Analytics <209478350+rinjanianalytics@users.noreply.github.com> Date: Sun, 11 Oct 2026 10:29:15 +0700 Subject: [PATCH] console: show the not found page for an unknown document An address like /docs/unknown is served by the documentation page, not by the not found page. It showed an empty table of contents and a red box, "Unable to fetch documentation page! Document not found.", with no way out. When the server answers 404 for a document, the documentation page now shows the same view as the not found page: what happened, and links to the home page and to the documentation. The title of the tab says "Not found". Other errors keep the red box. The view moves to its own component, shared by both pages. --- docs/99-changelog.md | 4 +-- web/src/app/docs/page.tsx | 24 ++++++++++++++-- web/src/app/not-found.tsx | 27 ++---------------- web/src/components/not-found.tsx | 48 ++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 29 deletions(-) create mode 100644 web/src/components/not-found.tsx diff --git a/docs/99-changelog.md b/docs/99-changelog.md index 14b3c35a..8ac0fbd0 100644 --- a/docs/99-changelog.md +++ b/docs/99-changelog.md @@ -29,8 +29,8 @@ identified with a specific icon: - 🌱 *console*: the user and the theme switch are at the foot of the menu. The bar above the pages is only shown on a small screen, where it opens the menu -- 🩹 *console*: the page for an unknown address says what happened and links - to the home page and to the documentation +- 🩹 *console*: the page for an unknown address or an unknown document says + what happened and links to the home page and to the documentation ## FlowScope 2.0.0-rs.0 - 2026-10-10 diff --git a/web/src/app/docs/page.tsx b/web/src/app/docs/page.tsx index 77e70444..c02b27c3 100644 --- a/web/src/app/docs/page.tsx +++ b/web/src/app/docs/page.tsx @@ -8,8 +8,16 @@ import { useQuery } from "@tanstack/react-query"; import { ChevronRight, CircleAlert, GitBranch } from "lucide-react"; import { SectionLabel } from "@/components/brand"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { NotFound } from "@/components/not-found"; import { useDocumentTitle, useServerConfig } from "@/components/providers"; -import { apiUrl, appPath, appUrl, basePath, type DocumentToc } from "@/lib/api"; +import { + ApiError, + apiUrl, + appPath, + appUrl, + basePath, + type DocumentToc, +} from "@/lib/api"; import { cn } from "@/lib/utils"; import { useScrollFade } from "@/hooks/use-scroll-fade"; @@ -66,7 +74,8 @@ export default function DocumentationPage() { }); const answer = await response.json().catch(() => null); if (!response.ok) { - throw new Error( + throw new ApiError( + response.status, answer?.message ?? `Server returned an error: ${response.status} ${response.statusText}`, ); @@ -87,6 +96,9 @@ export default function DocumentationPage() { [data], ); const toc = data?.toc ?? []; + // An address like /docs/unknown is served by this page: it says itself + // that there is nothing there. + const notFound = error instanceof ApiError && error.status === 404; // Only the current document is expanded when switching. The other ones // are toggled by the user. @@ -116,7 +128,7 @@ export default function DocumentationPage() { // The title comes from the rendered document. setTitle(content.current?.querySelector("h1")?.textContent ?? null); }, [html]); - useDocumentTitle(title); + useDocumentTitle(notFound ? "Not found" : title); // Hand over to the application the links it knows about, instead of // loading the whole page again. @@ -158,6 +170,12 @@ export default function DocumentationPage() { ); + if (notFound) { + return ( + navigate("intro", "")} /> + ); + } + return (