Frontend UX: typography hierarchy + a real page <h1> per route + per-route document.title #57
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: Medium. From a frontend UX audit.
Problems
text-sm(~61×) andtext-xs(~27×); only 3 headings exist in the whole app and they're inconsistent (login-page.tsx:34<h1 text-2xl>,object-detail.tsx:60<h2 text-xl>,vocabulary-terms.tsx:52<h3 text-sm uppercase>— an<h3>used as a caption). The list pages (objects, vocab, authorities, fields, search) have no page<h1>at all and no visible page title.web/index.htmlhardcodes<title>Collection</title>and nothing ever setsdocument.title(grep → zero). Every tab / history entry / bookmark reads "Collection" — power users keeping multiple object tabs can't tell them apart.Suggested fix
<h1>page title with consistent styling; stop using<h3>as an uppercase caption (use the shared label utility).document.titleper route (e.g.Objects — {object_number} | {app_name}) via a small hook in the shell / detail pages.Source: frontend UX/design audit, 2026-06-06.
Done — merged to
main(9b3a587).Typography hierarchy + page
<h1>:PageTitlecomponent (web/src/components/ui/page-title.tsx) — a semantic<h1>attext-2xl font-semibold tracking-tight. Each AppShell route now renders exactly one page<h1>(objects, object-new, vocabularies, authorities, fields, search), reusing existing i18n keys (nav.*/objects.new/fields.title) — no new strings.object_name) · body (text-sm) · caption (.label-caption).<h3>invocabulary-terms→<div className="label-caption">(it's a caption, not a heading). Exactly one<h1>per page preserved across all routes (master-detail: the list/search page owns the<h1>, the detail pane keeps its<h2>).Per-route
document.title:useDocumentTitlehook (web/src/lib/use-document-title.ts) sets"{Page} | {AppName}"(app name fromuseConfig), and restores the prior title on unmount./objects/:id,/search/:id) override the tab to the object'sobject_numberand revert when closed — so a curator's many object tabs are now distinguishable. (Resolved cleanly via an innerObjectDetailLoadedcomponent so the hook only runs post-load.)app.name, standalone/pre-auth).Pure additive UI — no layout/column restructure, no new dependency, en/sv parity untouched. Gate green: typecheck, lint, 186 tests, build, check:size (184.7 KB gz), check:colors; no codename.
Follow-ups (not in scope): header breadcrumb/wayfinding + global search entry (#54); broader per-screen density/spacing redesign.