# Phase 31: Mobile Polish — Research **Researched:** 2026-04-12 **Status:** Complete **Focus:** Icon-based action buttons on mobile detail pages ## Standard Stack - **Component library:** None (plain Tailwind CSS v4) - **Icon library:** lucide-react via `LucideIcon` component (`src/client/lib/iconData.tsx`) - **Styling:** Tailwind CSS v4 with `@import "tailwindcss"` (no custom tokens, no config file) - **Responsive pattern:** `md:` breakpoint (768px) — matches BottomTabBar (`md:hidden`) and TopNav (`hidden md:flex`) ## Action Button Inventory ### 1. Item Detail (`src/client/routes/items/$itemId.tsx`) **Location:** Top bar, right side (lines ~190-213) **Current pattern:** Text-only buttons in a `flex items-center gap-2` container **Edit mode:** Visible when `!isEditing` | Button | Text | Current Classes | Icon Candidate | |--------|------|----------------|----------------| | Duplicate | "Duplicate" | `px-3 py-1.5 text-sm text-gray-500 hover:text-gray-700 hover:bg-gray-50 rounded-lg` | `copy` (16px) | | Delete/Remove | "Delete" or "Remove from Collection" | `px-3 py-1.5 text-sm text-red-400 hover:text-red-600 hover:bg-red-50 rounded-lg` | `trash-2` (16px) | | Edit | "Edit" | `px-4 py-1.5 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 rounded-lg` | `pencil` (16px) | **Edit mode buttons (Cancel/Save):** These should remain text buttons even on mobile — users need clear text feedback during edit operations. ### 2. Candidate Detail (`src/client/routes/threads/$threadId/candidates/$candidateId.tsx`) **Location 1:** Header area — Edit button inline with heading (line ~282-289) **Current pattern:** Small text+icon button (`LucideIcon name="pencil" size={14}` + "Edit" text) **Location 2:** Bottom actions area (lines ~530-548) **Current pattern:** Text+icon buttons in `flex gap-3 pt-4 border-t border-gray-100` | Button | Text | Current Pattern | Icon Candidate | |--------|------|----------------|----------------| | Edit (header) | "Edit" | `px-3 py-1.5 text-sm` + pencil icon 14px | Already has icon — hide text on mobile | | Pick as Winner | "Pick as winner" | `px-4 py-2` + trophy icon 14px | `trophy` (16px) | | Delete | "Delete" | `px-4 py-2` + trash-2 icon 14px | Already has icon — hide text on mobile | ### 3. Setup Detail (`src/client/routes/setups/$setupId.tsx`) **Location:** Toolbar area below header (lines ~155-210) **Current pattern:** Mixed text+icon and text-only buttons in `flex items-center gap-3` | Button | Text | Current Pattern | Icon Candidate | |--------|------|----------------|----------------| | Add Items | "Add Items" | `px-4 py-2` + inline SVG plus icon | `plus` (16px) via LucideIcon | | Public toggle | "Public"/"Private" | `px-3 py-2` + inline SVG globe | `globe` (16px) via LucideIcon | | Delete Setup | "Delete Setup" | `px-4 py-2 text-red-600 bg-red-50` | `trash-2` (16px) | **Note:** Setup page uses inline SVGs instead of LucideIcon — migration to LucideIcon is a natural cleanup. ### 4. Global Item Detail (`src/client/routes/global-items/$globalItemId.tsx`) **Location:** Action buttons below image (lines ~167-193) **Current pattern:** Text-only buttons in `flex gap-3 mb-6` | Button | Text | Current Pattern | Icon Candidate | |--------|------|----------------|----------------| | Add to Collection | "Add to Collection" | `px-5 py-2.5 bg-gray-700 text-white` | `plus` (16px) | | Add to Thread | "Add to Thread" | `px-5 py-2.5 bg-white border` | `message-square-plus` (16px) | ## Architecture Patterns ### Recommended Implementation Pattern Use paired hidden/visible elements with responsive Tailwind classes: ```tsx {/* Desktop: text + optional icon */} {/* Mobile: icon-only with touch target */} ``` ### Alternative: Single Element with Responsive Text Hiding ```tsx ``` **Recommendation:** Use the paired-element approach for cleaner code and independent styling control. The single-element approach has too many responsive overrides. **Alternative considered and rejected:** A shared `IconActionButton` component. The action buttons across pages have different styling (primary, secondary, destructive), different sizes, and different hover states. A shared component would need too many props and wouldn't simplify the code meaningfully for just 4 pages. ### LucideIcon Migration for Setup Page The setup detail page uses inline SVGs for the plus icon and globe icon. These should be migrated to `LucideIcon` for consistency: - Plus SVG → `` - Globe SVG → `` ### Touch Target Sizing - Minimum 44x44px per WCAG 2.5.5 (AAA) / Apple HIG - Achieved with `min-w-[44px] min-h-[44px]` on mobile icon buttons - Desktop buttons keep current sizing (no min-width needed) ### Edit Mode Buttons Cancel and Save buttons during edit mode should **remain text buttons** on both mobile and desktop: - These are contextual actions that need clear text labels - Edit mode is a temporary state — users need to see "Cancel" and "Save" text clearly - No risk of button crowding since they replace the action buttons ## Dependencies None. This phase is self-contained — only modifies existing button rendering in 4 route files. ## Risks 1. **Low risk:** Button group layout may need adjustment on very small screens (< 375px) if multiple icon buttons overflow. Mitigation: test at 320px width. 2. **Low risk:** Missing `aria-label` would make icon buttons inaccessible. Mitigation: acceptance criteria require aria-label on every icon button. ## Validation Architecture ### Validation Strategy | Dimension | What to Validate | How | |-----------|-----------------|-----| | Visual | Icon buttons render on mobile, text on desktop | E2E viewport test or manual check | | Accessibility | All icon buttons have aria-label | Grep for aria-label on new button elements | | Touch targets | Minimum 44px on mobile | CSS class inspection (min-w-[44px] min-h-[44px]) | | Consistency | Same breakpoint (md:) across all pages | Grep for breakpoint usage | | No regression | Desktop buttons unchanged | Visual comparison | ## RESEARCH COMPLETE