6.6 KiB
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
LucideIconcomponent (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:
{/* Desktop: text + optional icon */}
<button className="hidden md:inline-flex items-center gap-1.5 px-3 py-1.5 text-sm ...">
<LucideIcon name="pencil" size={14} />
Edit
</button>
{/* Mobile: icon-only with touch target */}
<button
className="md:hidden inline-flex items-center justify-center min-w-[44px] min-h-[44px] p-2 rounded-lg ..."
aria-label="Edit"
title="Edit"
>
<LucideIcon name="pencil" size={16} />
</button>
Alternative: Single Element with Responsive Text Hiding
<button className="inline-flex items-center gap-1.5 px-2 md:px-3 py-1.5 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 justify-center md:justify-start ..." aria-label="Edit">
<LucideIcon name="pencil" size={16} className="md:w-3.5 md:h-3.5" />
<span className="hidden md:inline text-sm">Edit</span>
</button>
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 →
<LucideIcon name="plus" size={16} /> - Globe SVG →
<LucideIcon name="globe" size={16} />
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
- Low risk: Button group layout may need adjustment on very small screens (< 375px) if multiple icon buttons overflow. Mitigation: test at 320px width.
- Low risk: Missing
aria-labelwould 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 |