feat(uat-batch-10): copy polish, TTL trim, and a11y discrete fixes

- Supplemental-info link TTL trimmed from 30 → 14 days (single
  constant in supplemental-forms.service).
- LinkedBerthsList toggle renamed "Mark in EOI bundle" →
  "Include in EOI"; tooltip aria-label updated to match.
- Icon-only row-action triggers on the interest / client / berth list
  tables gain aria-label (Row actions for <name>) so SR users hear
  the row context.
- Table / Board view toggle on interest list gains aria-label +
  aria-pressed on each variant; wrapper gets role="group".
- Upcoming-milestones disclosure on interest-tabs gains
  aria-expanded + aria-controls; recommender Hide/Add filters
  button matches.
- BrandedAuthShell logo alt no longer defaults to "Sign in" — uses
  the configured `appName` when known, empty string otherwise so
  screen readers don't announce "Sign in" on password-reset /
  set-password pages.

tsc clean. 1419/1419 vitest pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-21 18:01:17 +02:00
parent 5f937b4551
commit db511063df
9 changed files with 28 additions and 6 deletions

View File

@@ -176,6 +176,7 @@ function ActionsCell({ row }: { row: { original: BerthRow } }) {
variant="ghost"
size="icon"
className="h-8 w-8"
aria-label={`Row actions for berth ${berth.mooringNumber ?? ''}`.trim()}
onClick={(e) => e.stopPropagation()}
>
<MoreHorizontal className="h-4 w-4" aria-hidden />

View File

@@ -302,6 +302,7 @@ export function getClientColumns({
variant="ghost"
size="icon"
className="h-7 w-7"
aria-label={`Row actions for ${row.original.fullName ?? 'client'}`}
onClick={(e) => e.stopPropagation()}
>
<MoreHorizontal className="h-4 w-4" aria-hidden />

View File

@@ -503,6 +503,8 @@ export function BerthRecommenderPanel({
variant="outline"
onClick={() => setFiltersOpen((v) => !v)}
disabled={!hasDimensions}
aria-expanded={filtersOpen}
aria-controls="recommender-filters-body"
>
<Filter className="mr-1.5 size-3.5" aria-hidden />
{filtersOpen ? 'Hide filters' : 'Add filters'}
@@ -542,7 +544,9 @@ export function BerthRecommenderPanel({
</div>
</div>
{!collapsed && filtersOpen && hasDimensions ? (
<AmenityFilterForm filters={amenityFilters} onChange={setAmenityFilters} />
<div id="recommender-filters-body">
<AmenityFilterForm filters={amenityFilters} onChange={setAmenityFilters} />
</div>
) : null}
{!collapsed && hasDimensions && areaChips.length > 1 ? (
<div className="flex flex-wrap items-center gap-1.5 pt-1">

View File

@@ -297,6 +297,7 @@ export function getInterestColumns({
variant="ghost"
size="icon"
className="h-7 w-7"
aria-label={`Row actions for ${row.original.clientName ?? 'interest'}`}
onClick={(e) => e.stopPropagation()}
>
<MoreHorizontal className="h-4 w-4" aria-hidden />

View File

@@ -183,11 +183,17 @@ export function InterestList() {
{/* Kanban view is desktop-only — mobile drops the toggle and
falls back to the list/cards view (the board's column
horizontal-scroll model is unusable at phone widths). */}
<div className="hidden sm:flex items-center border rounded-md overflow-hidden">
<div
className="hidden sm:flex items-center border rounded-md overflow-hidden"
role="group"
aria-label="View mode"
>
<Button
size="sm"
variant={viewMode === 'table' ? 'default' : 'ghost'}
className="rounded-none"
aria-label="Table view"
aria-pressed={viewMode === 'table'}
onClick={() => setViewMode('table')}
>
<LayoutList className="h-4 w-4" aria-hidden />
@@ -196,6 +202,8 @@ export function InterestList() {
size="sm"
variant={viewMode === 'board' ? 'default' : 'ghost'}
className="rounded-none"
aria-label="Board view"
aria-pressed={viewMode === 'board'}
onClick={() => setViewMode('board')}
>
<Kanban className="h-4 w-4" aria-hidden />

View File

@@ -555,6 +555,8 @@ function FutureMilestones({
<button
type="button"
onClick={() => setExpanded((v) => !v)}
aria-expanded={expanded}
aria-controls="future-milestones-body"
className="flex w-full items-center justify-between gap-2 px-4 py-2.5 text-sm text-muted-foreground hover:text-foreground hover:bg-muted/30 transition-colors"
>
<span>
@@ -565,6 +567,7 @@ function FutureMilestones({
</button>
{expanded && (
<div
id="future-milestones-body"
className={cn(
'grid grid-cols-1 gap-4 p-4 pt-0',
milestones.length === 1 ? '' : 'lg:grid-cols-2',

View File

@@ -374,14 +374,14 @@ function LinkedBerthRowItem({
htmlFor={`bundle-${row.berthId}`}
className="text-sm font-medium cursor-pointer"
>
Mark in EOI bundle
Include in EOI
</Label>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
className="inline-flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground hover:bg-muted/60 hover:text-foreground"
aria-label="What does Mark in EOI bundle do?"
aria-label="What does Include in EOI do?"
>
<HelpCircle className="h-3.5 w-3.5" aria-hidden />
</button>

View File

@@ -29,7 +29,11 @@ export function BrandedAuthShell({ children, branding }: BrandedAuthShellProps)
const ctx = useAuthBranding();
const logoUrl = branding?.logoUrl ?? ctx?.logoUrl ?? null;
const backgroundUrl = branding?.backgroundUrl ?? ctx?.backgroundUrl ?? null;
const altText = branding?.appName ?? ctx?.appName ?? 'Sign in';
// When no port name is known, treat the logo as decorative — "Sign in"
// as alt text was being read on every auth page even when the page
// itself isn't a sign-in surface (e.g. password reset, set-password).
const appName = branding?.appName ?? ctx?.appName ?? null;
const altText = appName ?? '';
// fixed inset-0 anchors the auth surface to the viewport directly —
// iOS Safari ignores overflow-hidden on inner divs for body-level
// scrolling, so a regular `h-dvh overflow-hidden` wrapper doesn't