Frontend UX: app header is empty — add wayfinding (section/breadcrumb, user, app name, global search) #54

Closed
opened 2026-06-06 18:52:28 +00:00 by logaritmisk · 1 comment
Owner

Severity: Medium. From a frontend UX audit. The header is the most persistent surface in a hours-a-day tool and currently carries almost nothing.

Problems

  • The <header> (web/src/shell/app-shell.tsx:66-72) contains only a flex spacer, the language switch, and Sign out — no section title/breadcrumb, no logged-in user identity, no global-search entry.
  • No "where am I" on nested/create routes: deep-linking to /objects/new shows the Objects list with nothing selected and no header cue that you're in create mode (no breadcrumb/page title anywhere).
  • The configured instance name is dead data. ConfigProvider fetches /api/config app_name, but the sidebar brand (app-shell.tsx:21) and login heading (login-page.tsx:34) render the hardcoded t("app.name") = "Collection" instead of useConfig().app_name (default "Collection Management System"). Two competing names; the configured one appears nowhere.
  • Global search is a sidebar nav item requiring a full route change away from the record you're editing.

Suggested fixes

  • Put a route-driven section title/breadcrumb on the header's left (filling the empty spacer), e.g. "Objects / New" or "Objects / {object_number}".
  • Show the signed-in user (useMe() already returns it) and a quick-search affordance on the right.
  • Render useConfig().app_name for the brand + login heading; drop/repurpose the hardcoded app.name.

Source: frontend UX/design audit, 2026-06-06.

**Severity: Medium.** _From a frontend UX audit. The header is the most persistent surface in a hours-a-day tool and currently carries almost nothing._ ## Problems - The `<header>` (`web/src/shell/app-shell.tsx:66-72`) contains only a flex spacer, the language switch, and Sign out — no section title/breadcrumb, no logged-in user identity, no global-search entry. - **No "where am I" on nested/create routes:** deep-linking to `/objects/new` shows the Objects list with nothing selected and no header cue that you're in create mode (no breadcrumb/page title anywhere). - **The configured instance name is dead data.** `ConfigProvider` fetches `/api/config` `app_name`, but the sidebar brand (`app-shell.tsx:21`) and login heading (`login-page.tsx:34`) render the hardcoded `t("app.name")` = "Collection" instead of `useConfig().app_name` (default "Collection Management System"). Two competing names; the configured one appears nowhere. - Global search is a sidebar nav item requiring a full route change away from the record you're editing. ## Suggested fixes - Put a route-driven section title/breadcrumb on the header's left (filling the empty spacer), e.g. "Objects / New" or "Objects / {object_number}". - Show the signed-in user (`useMe()` already returns it) and a quick-search affordance on the right. - Render `useConfig().app_name` for the brand + login heading; drop/repurpose the hardcoded `app.name`. _Source: frontend UX/design audit, 2026-06-06._
Author
Owner

Done — merged to main (b7242ca). The header now carries real wayfinding.

Breadcrumb (left): a page-driven useBreadcrumb(trail) hook + BreadcrumbProvider (parallel to the #57 title hook) — every AppShell route sets its trail and the header renders it (non-leaf crumbs are links):

  • list pages → Objects / Vocabularies / …; Objects / New, Objects / Edit; Objects / {object_number}; Vocabularies / {key}.
  • Dynamic labels come from already-loaded data (object_number; vocab .key via the cache-shared useVocabularies), so no extra requests and no UUIDs in the crumb.

User menu (right): a new reusable ui/menu.tsx Base UI dropdown wrapper (validated by running) powers a UserMenu showing the signed-in email + role and a Sign out item; the standalone Sign out button is gone (logout lives in the menu).

Header search (right): a compact HeaderSearch input → /search?q=… (encoded), so you can start a search from any screen; hidden below sm. (The full ⌘K palette / in-place modal remains #33.)

Brand + login: the sidebar brand and login heading + tab title now render useConfig().app_name (default "Collection Management System"); the hardcoded app.name i18n key is removed from both locales.

No new dependency (Base UI Menu is a namespace export of the existing @base-ui/react); i18n net +1/−1 with en/sv parity; the breadcrumb hook is eslint-disable-free (ref + serialized-key pattern). Gate green: typecheck, lint, 194 tests, build, check:size (196.3 KB gz, +~12 KB for Menu, under the 250 KB budget), check:colors; no codename.

Follow-ups (out of scope): full ⌘K command palette (#33); broader responsive header (#58); /search/:id showing a search-relative (vs canonical-object) breadcrumb; user avatar / account settings page.

Done — merged to `main` (`b7242ca`). The header now carries real wayfinding. **Breadcrumb (left):** a page-driven `useBreadcrumb(trail)` hook + `BreadcrumbProvider` (parallel to the #57 title hook) — every AppShell route sets its trail and the header renders it (non-leaf crumbs are links): - list pages → `Objects` / `Vocabularies` / …; `Objects / New`, `Objects / Edit`; `Objects / {object_number}`; `Vocabularies / {key}`. - Dynamic labels come from already-loaded data (object_number; vocab `.key` via the cache-shared `useVocabularies`), so no extra requests and no UUIDs in the crumb. **User menu (right):** a new reusable `ui/menu.tsx` Base UI dropdown wrapper (validated by running) powers a `UserMenu` showing the signed-in `email` + `role` and a **Sign out** item; the standalone Sign out button is gone (logout lives in the menu). **Header search (right):** a compact `HeaderSearch` input → `/search?q=…` (encoded), so you can start a search from any screen; hidden below `sm`. (The full ⌘K palette / in-place modal remains #33.) **Brand + login:** the sidebar brand and login heading + tab title now render `useConfig().app_name` (default "Collection Management System"); the hardcoded `app.name` i18n key is removed from both locales. No new dependency (Base UI Menu is a namespace export of the existing `@base-ui/react`); i18n net +1/−1 with en/sv parity; the breadcrumb hook is `eslint-disable`-free (ref + serialized-key pattern). Gate green: typecheck, lint, **194 tests**, build, check:size (196.3 KB gz, +~12 KB for Menu, under the 250 KB budget), check:colors; no codename. Follow-ups (out of scope): full ⌘K command palette (#33); broader responsive header (#58); `/search/:id` showing a search-relative (vs canonical-object) breadcrumb; user avatar / account settings page.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logaritmisk/biggus-dickus#54