Below md, the Users directory and the three Backups tables render as
MobileCardRow cards:
- UsersPage: display name primary; username/activity/email fields. Each
card carries a selection checkbox (44px via mobile-touch-target) in the
actions slot with stopPropagation so toggling selection does not open
the drawer; card-body tap still opens the drawer.
- BackupAlertsTable: alert message primary; severity/type/created fields;
Acknowledge action preserved in actions slot.
- BackupJobsTable: job name primary; source/schedule/last-status fields
(joins latestRuns into a JobCardRow).
- BackupRunsTable: run job_id primary; status/duration/size/started fields;
status-filter Select renders above both layouts (preserved on mobile).
Desktop (md+) is byte-for-byte identical for all four components -- the
UsersPage diff is dominated by re-indenting the existing Table into the
isMobile ternary else branch.
Fix from Slice 5 review: MobileCardRow now renders the clickable card as
<div role=button tabIndex=0> with Enter/Space keyboard handling instead
of <button>, so nesting a Radix Checkbox (which renders a <button>) in
the actions slot produces valid HTML. The desktop-parity argument for
<button>-in-<button> did not hold (desktop rows are <tr>, not buttons).
Cross-cutting: useIsMobile hardened with typeof window.matchMedia guard
(safe in real browsers; only changes jsdom crash -> false). The file-local
900px compose hook was renamed useComposeViewport to avoid collision with
the shared 768px useIsMobile.
Tests: BackupJobsTable test file added (was untested), UsersPage mobile
selection round-trip + stopPropagation, mobile card render across all
four components. 105 tests pass; lint/build green.
Refs openspec/changes/mobile-responsive-parity/ (spec R3, tasks slice 5).