feat(ux): per-view subtitles, correspondence cross-links, mobile kanban, a11y

Continuing the product/UX review's deferred items:

- Every top-level view now gets a one-line subtitle under its title
  (Dashboard/Jobs/Kanban/Reminders/Correspondence/Gmail review) stating
  what that specific view is for, instead of navigation being the only
  signal of what each page does.
- Correspondence inbox and Gmail review queue cross-link to each other
  instead of being two unexplained flat sidebar items -- kept both nav
  entries (renaming/nesting risked breaking muscle memory) but made the
  relationship between them explicit in the UI itself.
- Kanban board switches to a horizontal scroll-snap row on phone-width
  viewports instead of stacking all 5 columns vertically, which meant
  a lot of scrolling to see anything past "Applied".
- Match-score ring gets an aria-label with the actual percentage --
  it was two nested decorative CircularProgress elements with no
  accessible text. (Keyboard-accessible status changes on kanban cards
  were already covered by the existing "..." menu -- no gap there.)
This commit is contained in:
cesnimda
2026-07-12 04:14:37 +02:00
parent 58868fc2b6
commit 9615ee3f41
7 changed files with 51 additions and 4 deletions
+12
View File
@@ -100,6 +100,16 @@ function titleFor(path: string, t: (k: any) => string): string {
return t("appTitle");
}
function subtitleFor(path: string, t: (k: any) => string): string | undefined {
if (path === "/dashboard") return t("dashboardPageSubtitle");
if (path.startsWith("/jobs")) return t("jobsPageSubtitle");
if (path.startsWith("/kanban")) return t("kanbanPageSubtitle");
if (path.startsWith("/reminders")) return t("remindersPageSubtitle");
if (path.startsWith("/correspondence/review")) return t("gmailReviewPageSubtitle");
if (path.startsWith("/correspondence")) return t("correspondencePageSubtitle");
return undefined;
}
function PageLoader() {
return <Box sx={{ p: 4 }}><Typography variant="h6">Loading...</Typography></Box>;
}
@@ -209,6 +219,7 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo
if (requireAuth && !me) return <Navigate to="/" replace state={{ from: path }} />;
const pageTitle = titleFor(path, t);
const pageSubtitle = subtitleFor(path, t);
const breadcrumbs = breadcrumbsFor(path, t);
const setAndPersistPageSize = (n: 15 | 20 | 25) => { setJobPageSize(n); window.localStorage.setItem("jobPageSize", String(n)); };
const setAndPersistColumns = (next: JobTableColumns) => { setJobColumns(next); window.localStorage.setItem("jobColumns", JSON.stringify(next)); };
@@ -275,6 +286,7 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo
<>
<AppShell
pageTitle={pageTitle}
pageSubtitle={pageSubtitle}
breadcrumbs={breadcrumbs}
pathname={path}
nav={nav}