From 017e5774b251b9a398de6f078958424df8363489 Mon Sep 17 00:00:00 2001 From: codevector96 Date: Mon, 13 Jul 2026 16:09:51 -0700 Subject: [PATCH 1/3] feat(ui): make maintainer data tables responsive and keyboard-accessible (#794) Adds a shared TableScroll primitive: a focusable (tabIndex 0), role=region, aria-label'd horizontal-scroll container so wide tables are scrollable by keyboard alone (WCAG 2.1.1), not just pointer. Adopts it on the maintainer quality table and the two maintainer-panel tables (reviewability queue + permission remediation), which previously had no scroll region at all, and adds the missing scope="col" headers and an sr-only to each so assistive tech announces the columns and a table name. No behavior/logic change; apps/gittensory-ui only. --- .../app-panels/contributor-quality-table.tsx | 20 ++- .../site/app-panels/maintainer-panel.tsx | 155 +++++++++++------- .../src/components/site/data-table.test.tsx | 44 +++++ .../src/components/site/data-table.tsx | 33 ++++ 4 files changed, 185 insertions(+), 67 deletions(-) create mode 100644 apps/gittensory-ui/src/components/site/data-table.test.tsx create mode 100644 apps/gittensory-ui/src/components/site/data-table.tsx diff --git a/apps/gittensory-ui/src/components/site/app-panels/contributor-quality-table.tsx b/apps/gittensory-ui/src/components/site/app-panels/contributor-quality-table.tsx index cdf66a8f42..70ab373b60 100644 --- a/apps/gittensory-ui/src/components/site/app-panels/contributor-quality-table.tsx +++ b/apps/gittensory-ui/src/components/site/app-panels/contributor-quality-table.tsx @@ -1,4 +1,5 @@ import { BoundaryBadge, StatusPill } from "@/components/site/control-primitives"; +import { TableScroll } from "@/components/site/data-table"; import { EmptyState } from "@/components/site/state-views"; import { QUALITY_BAND_TONE, @@ -30,13 +31,22 @@ export function ContributorQualityTable({ description="Quality bands appear once this maintainer's repos have cached open pull requests to shape." /> ) : ( -
+ + - - - + + + @@ -58,7 +68,7 @@ export function ContributorQualityTable({ ))}
+ Contributors with their quality band and open pull request count. +
ContributorBandOpen PRs + Contributor + + Band + + Open PRs +
-
+ )} ); diff --git a/apps/gittensory-ui/src/components/site/app-panels/maintainer-panel.tsx b/apps/gittensory-ui/src/components/site/app-panels/maintainer-panel.tsx index b8be6d289d..76bf0f4360 100644 --- a/apps/gittensory-ui/src/components/site/app-panels/maintainer-panel.tsx +++ b/apps/gittensory-ui/src/components/site/app-panels/maintainer-panel.tsx @@ -34,6 +34,7 @@ import { MaintainerSettings } from "@/components/site/app-panels/maintainer-sett import { OnboardingPreviewCard } from "@/components/site/app-panels/onboarding-preview-card"; import { CheckRunReadinessTable } from "@/components/site/check-run-readiness-table"; import type { CheckRunReadinessTableData } from "@/components/site/check-run-readiness-model"; +import { TableScroll } from "@/components/site/data-table"; import { StatCard } from "@/components/site/primitives"; import { RefreshMeta } from "@/components/site/refresh-meta"; import { EmptyState, LoadingState, StateBoundary } from "@/components/site/state-views"; @@ -340,52 +341,71 @@ function MaintainerDashboardView({ private - - - - - - - - - - - - - {data.reviewability.map((row) => ( - - - - - - - + +
PRTitleAuthorBucketSlopReason
- {row.pr} - {row.title}{row.author} - - {row.bucket} - - - {row.slop ? ( - - {row.slop.band} {row.slop.risk} - - ) : ( - - — - - )} - {row.reason}
+ + + + + + + + + - ))} - -
+ Reviewable pull requests with bucket, slop band, and reason. +
+ PR + + Title + + Author + + Bucket + + Slop + + Reason +
+ + + {data.reviewability.map((row) => ( + + + {row.pr} + + {row.title} + + {row.author} + + + + {row.bucket} + + + + {row.slop ? ( + + {row.slop.band} {row.slop.risk} + + ) : ( + + — + + )} + + {row.reason} + + ))} + + + @@ -717,24 +737,35 @@ export function PreviewResult({ {preview.installation?.permissionRemediation.length ? (
- - - - - - - - - - {preview.installation.permissionRemediation.map((row) => ( - - - - + +
PermissionCurrentRequired
{row.permission}{row.currentAccess}{row.requiredAccess}
+ + + + + + - ))} - -
+ GitHub App permission remediation: current versus required access per permission. +
+ Permission + + Current + + Required +
+ + + {preview.installation.permissionRemediation.map((row) => ( + + {row.permission} + {row.currentAccess} + {row.requiredAccess} + + ))} + + +
) : null} diff --git a/apps/gittensory-ui/src/components/site/data-table.test.tsx b/apps/gittensory-ui/src/components/site/data-table.test.tsx new file mode 100644 index 0000000000..255cf1e66f --- /dev/null +++ b/apps/gittensory-ui/src/components/site/data-table.test.tsx @@ -0,0 +1,44 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import { TableScroll } from "@/components/site/data-table"; + +describe("TableScroll", () => { + it("wraps its table in a keyboard-focusable, labelled scroll region (WCAG 2.1.1)", () => { + render( + + + + + + + + +
Example data rows
cell
+
, + ); + const region = screen.getByRole("region", { name: "Example data" }); + // A bare overflow-x-auto div is not a tab stop; this one is, so keyboard users can scroll it. + expect(region.tabIndex).toBe(0); + expect(region.className).toContain("overflow-x-auto"); + // The inner table takes its accessible name from the caption, not the region label. + expect(screen.getByRole("table", { name: "Example data rows" })).toBeTruthy(); + }); + + it("merges a caller className onto the scroll region without dropping the base classes", () => { + render( + + + + + + + +
x
+
, + ); + const region = screen.getByRole("region", { name: "Wide table" }); + expect(region.className).toContain("mt-4"); + expect(region.className).toContain("overflow-x-auto"); + }); +}); diff --git a/apps/gittensory-ui/src/components/site/data-table.tsx b/apps/gittensory-ui/src/components/site/data-table.tsx new file mode 100644 index 0000000000..a9f94eb6c7 --- /dev/null +++ b/apps/gittensory-ui/src/components/site/data-table.tsx @@ -0,0 +1,33 @@ +import type { ReactNode } from "react"; + +import { cn } from "@/lib/utils"; + +/** + * Accessible horizontal-scroll container for a wide data table (#794). A table that overflows its + * column area on a narrow viewport can be scrolled by pointer/trackpad but NOT by keyboard alone: + * marking the scroll region focusable (`tabIndex={0}`) with `role="region"` + an `aria-label` gives + * keyboard users a real tab stop to scroll from (WCAG 2.1.1 Keyboard) and names the region for + * assistive tech. Pair with a `` and `scope="col"` headers on the table inside — the three + * together are the standard responsive-table a11y pattern this app's bare `overflow-x-auto` divs and + * caption-less tables were missing. + */ +export function TableScroll({ + label, + className, + children, +}: { + label: string; + className?: string; + children: ReactNode; +}) { + return ( +
+ {children} +
+ ); +} From a8bdeb1803c6cf3b9a633dd6d772d75cbc68ad23 Mon Sep 17 00:00:00 2001 From: codevector96 Date: Mon, 13 Jul 2026 21:38:30 -0700 Subject: [PATCH 2/3] fix(ui): keep status badges and table cells on one line (#794) Owner review follow-up: long badge labels (e.g. NEEDS-AUTHOR, Public-safe) wrapped onto multiple lines and mobile table cells wrapped too, which read as rough. Add whitespace-nowrap to StatusPill/BoundaryBadge (with shrink-0 dots) so labels never wrap, and to the maintainer data tables so cells stay single-line and scroll inside the TableScroll region instead of wrapping. --- .../site/app-panels/contributor-quality-table.tsx | 2 +- .../src/components/site/app-panels/maintainer-panel.tsx | 4 ++-- .../src/components/site/control-primitives.tsx | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/gittensory-ui/src/components/site/app-panels/contributor-quality-table.tsx b/apps/gittensory-ui/src/components/site/app-panels/contributor-quality-table.tsx index 70ab373b60..0204d3ddd5 100644 --- a/apps/gittensory-ui/src/components/site/app-panels/contributor-quality-table.tsx +++ b/apps/gittensory-ui/src/components/site/app-panels/contributor-quality-table.tsx @@ -32,7 +32,7 @@ export function ContributorQualityTable({ /> ) : ( - +
diff --git a/apps/gittensory-ui/src/components/site/app-panels/maintainer-panel.tsx b/apps/gittensory-ui/src/components/site/app-panels/maintainer-panel.tsx index 76bf0f4360..f79ed8c1ff 100644 --- a/apps/gittensory-ui/src/components/site/app-panels/maintainer-panel.tsx +++ b/apps/gittensory-ui/src/components/site/app-panels/maintainer-panel.tsx @@ -342,7 +342,7 @@ function MaintainerDashboardView({ -
Contributors with their quality band and open pull request count.
+
@@ -738,7 +738,7 @@ export function PreviewResult({ {preview.installation?.permissionRemediation.length ? (
-
Reviewable pull requests with bucket, slop band, and reason.
+
diff --git a/apps/gittensory-ui/src/components/site/control-primitives.tsx b/apps/gittensory-ui/src/components/site/control-primitives.tsx index 2f61b308e7..a80cf3725b 100644 --- a/apps/gittensory-ui/src/components/site/control-primitives.tsx +++ b/apps/gittensory-ui/src/components/site/control-primitives.tsx @@ -26,12 +26,12 @@ export function StatusPill({ return ( - + {children} ); @@ -53,12 +53,12 @@ export function BoundaryBadge({ boundary, className }: { boundary: Boundary; cla return ( - + {label} ); From 7b0095bb422da59a03c662b7cc2986f2f896df12 Mon Sep 17 00:00:00 2001 From: codevector96 Date: Mon, 13 Jul 2026 21:59:10 -0700 Subject: [PATCH 3/3] fix(ui): make status badges more compact (#794) Owner review follow-up: the pills read too large in the tables. Shrink StatusPill and BoundaryBadge to a 10px font with tighter padding/gap and a smaller dot, so they fit the dense maintainer tables. Colors (STATUS_STYLES / per-status tones) are unchanged. --- .../src/components/site/control-primitives.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/gittensory-ui/src/components/site/control-primitives.tsx b/apps/gittensory-ui/src/components/site/control-primitives.tsx index a80cf3725b..68cc377f07 100644 --- a/apps/gittensory-ui/src/components/site/control-primitives.tsx +++ b/apps/gittensory-ui/src/components/site/control-primitives.tsx @@ -26,12 +26,12 @@ export function StatusPill({ return ( - + {children} ); @@ -53,7 +53,7 @@ export function BoundaryBadge({ boundary, className }: { boundary: Boundary; cla return (
GitHub App permission remediation: current versus required access per permission.