feat(web): page <h1> + document.title on list/form routes (#57)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 17:17:01 +02:00
parent 70025e1e71
commit 6e1f5ea50f
7 changed files with 81 additions and 25 deletions
+5
View File
@@ -4,6 +4,8 @@ import { useTranslation } from "react-i18next";
import { ObjectForm, type ObjectFormValues } from "./object-form";
import { useCreateObject, useSetFields, FieldRejection } from "../api/queries";
import { useDocumentTitle } from "../lib/use-document-title";
import { PageTitle } from "@/components/ui/page-title";
export function ObjectNewPage() {
const { t } = useTranslation();
@@ -12,6 +14,8 @@ export function ObjectNewPage() {
const setFields = useSetFields();
const [error, setError] = useState<string | null>(null);
useDocumentTitle(t("objects.new"));
const onSubmit = async (values: ObjectFormValues) => {
setError(null);
@@ -44,6 +48,7 @@ export function ObjectNewPage() {
return (
<div className="mx-auto max-w-2xl">
<PageTitle className="mb-4">{t("objects.new")}</PageTitle>
<ObjectForm
mode="create"
formError={error}
+11 -1
View File
@@ -1,5 +1,5 @@
import { afterEach, expect, test, vi } from "vitest";
import { screen, within } from "@testing-library/react";
import { screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Routes, Route } from "react-router-dom";
import { renderApp } from "../test/render";
@@ -39,6 +39,16 @@ afterEach(() => {
vi.restoreAllMocks();
});
test("renders the page heading and sets the document title", async () => {
setViewport(true);
renderApp(tree(), { route: "/objects" });
expect(
await screen.findByRole("heading", { level: 1, name: /objects/i }),
).toBeInTheDocument();
await waitFor(() => expect(document.title).toMatch(/objects \| /i));
});
test("the table is the landing view; no detail panel until a row is opened", async () => {
setViewport(true);
renderApp(tree(), { route: "/objects" });
+9 -2
View File
@@ -5,6 +5,8 @@ import { X } from "lucide-react";
import { ObjectsTable } from "./objects-table";
import { useMediaQuery } from "../lib/use-media-query";
import { useDocumentTitle } from "../lib/use-document-title";
import { PageTitle } from "@/components/ui/page-title";
const ObjectDetailDrawer = lazy(() =>
import("./object-detail-drawer").then((m) => ({ default: m.ObjectDetailDrawer })),
@@ -23,11 +25,16 @@ export function ObjectsPage() {
const open = Boolean(detailMatch ?? editMatch);
const isWide = useMediaQuery("(min-width: 1024px)");
useDocumentTitle(t("nav.objects"));
const closeDetail = () => navigate(`/objects?${searchParams}`);
const table = (
<div className="overflow-hidden">
<ObjectsTable />
<div className="flex h-full flex-col overflow-hidden">
<PageTitle className="px-4 pt-4 pb-2">{t("nav.objects")}</PageTitle>
<div className="flex-1 overflow-hidden">
<ObjectsTable />
</div>
</div>
);