Foundation for the mobile-responsive-parity change. Adds:
- useIsMobile() hook: single source of truth for the md:768px cut (SSR-safe)
- MobileCardRow<T>: stacked card list for wide tables below md, with getRowId
stable keys, primary field as title, optional onRowClick + actions slot
- SheetForm: full-height form host (h-[100dvh], flex column, sticky header +
footer via flex not position:sticky) for mobile edit flows
- HoverEditButton: mobile prop (default 'always') -- always visible below md,
hover-revealed at md+; desktop aesthetic preserved
- .mobile-touch-target CSS utility: 44x44 min hit area below md (WCAG 2.5.5)
- App.tsx refactored to use useIsMobile(); shell behavior unchanged
Tests cover primary/field rendering, onRowClick, actions slot, empty rows,
no-primary, stable keys (no duplicate-key warning), and all SheetForm
interactions. 86 tests pass; lint/build green.
MobileCardRow key strategy: uses getRowId when provided (falls back to index);
per design §trade-offs, fields are declared per-table to prioritize by mobile
importance rather than auto-derived from column defs.
Refs openspec/changes/mobile-responsive-parity/ (design §Shared primitives,
spec R1/R5/R6, tasks slice 1).