fix(mobile): clipping, dropdown-tabs and stale phone metadata

Five mobile-UX issues caught in the 2026-05-03 audit, fixed in one pass:

1. SpecRow on berth detail clipped at right edge on phone widths.
   "Length 49.21 ft / 15 r" (the "m" cut off). Mobile-first stack:
   label on top, value full-width below; flex row only from sm up.

2. ResponsiveTabs collapsed to a Select on phone widths, which read like
   a generic dropdown and obscured the existence of peer tabs. Replaced
   with a horizontally-scrollable strip that auto-scrolls the active
   trigger into view (so the user sees neighbors and gets a discovery
   cue that more exists beyond the edge). Removes the phone-only Select
   and unifies the tab UI across viewport sizes.

3. Documents page tab strip ("All / EOI queue / Awaiting them / ...")
   overflowed the 390px viewport because the wrapper was a fixed flex
   row. Same horizontal-scroll fix as (2); inherits because Documents
   uses ResponsiveTabs.

4. Berth detail header: "Change Status" + "Edit" buttons crowded the
   area subtitle on mobile, causing "North Pier" to wrap to two lines
   ("North" / "Pier"). Stacked vertically on phone widths; from sm up
   the buttons sit beside the title block as before.

5. Empty contact rows on client detail rendered a stale "Add tag · star"
   metadata strip even when the contact value was unset, which cluttered
   the row and offered no useful action. The metadata block now only
   shows when contact.value is non-empty; the trash icon stays visible
   so users can clean up the empty placeholder.

Verification:
- pnpm exec vitest run: 858/858 passing
- pnpm exec tsc --noEmit: same 36 errors as baseline (all pre-existing
  on feat/mobile-foundation, none introduced)
- lint clean

Defers:
- Mobile More sheet last-row alignment / "Email" label specificity
- Admin index grouping (Access / System / Configuration / Content)
- Interest detail header icon labels (trophy/X discoverability)
- Pipeline funnel x-axis label abbreviations
- Reminders rail width allocation on dashboard

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-03 16:03:56 +02:00
parent e2398099c4
commit cad55e3565
4 changed files with 87 additions and 71 deletions

View File

@@ -167,7 +167,10 @@ export function BerthDetailHeader({ berth }: BerthDetailHeaderProps) {
return (
<>
<DetailHeaderStrip>
<div className="flex items-start gap-4">
{/* Stacks vertically on phone widths so the action buttons don't
squeeze the area subtitle into a two-line wrap. From sm up the
title/area block sits side-by-side with the action buttons. */}
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 flex-wrap">
<h1 className="hidden sm:block text-2xl font-bold text-foreground">
@@ -182,7 +185,7 @@ export function BerthDetailHeader({ berth }: BerthDetailHeaderProps) {
{berth.area && <p className="text-muted-foreground mt-1">{berth.area}</p>}
</div>
<div className="flex flex-wrap items-center gap-2 shrink-0">
<div className="flex flex-wrap items-center gap-2 sm:shrink-0">
<PermissionGate resource="berths" action="edit">
<Button variant="outline" size="sm" onClick={() => setStatusOpen(true)}>
<RefreshCw className="mr-1.5 h-4 w-4" />