feat: US-014 - Frontend - Mobile responsive and touch optimization

This commit is contained in:
Echo
2026-02-10 17:30:22 +00:00
parent dfc2229091
commit ae06e84070
3 changed files with 238 additions and 2 deletions

View File

@@ -86,6 +86,96 @@
.filter-group {
width: 100%;
}
/* Mobile touch targets - minimum 44px */
.habit-card-action-btn {
min-width: 44px;
min-height: 44px;
}
.habit-card-check-btn {
min-height: 48px;
font-size: var(--text-lg);
}
.habit-card-skip-btn {
min-height: 44px;
padding: var(--space-2) var(--space-3);
}
.modal-close {
min-width: 44px;
min-height: 44px;
}
/* Stats row 2x2 on mobile */
.stats-row {
grid-template-columns: repeat(2, 1fr);
}
/* Icon and color pickers wrap properly */
.color-picker-swatches {
grid-template-columns: repeat(4, 1fr);
}
.icon-picker-grid {
grid-template-columns: repeat(4, 1fr);
max-height: 300px;
}
/* Day checkboxes wrap on small screens */
.day-checkboxes {
grid-template-columns: repeat(4, 1fr);
}
/* Modal padding adjustment */
.modal {
margin: var(--space-2);
}
.modal-body,
.modal-header,
.modal-footer {
padding: var(--space-3);
}
/* Touch-friendly form elements */
.form-input,
.form-select,
.form-textarea {
min-height: 44px;
font-size: var(--text-base);
}
/* Larger touch targets for pickers */
.color-swatch {
min-height: 44px;
}
.icon-option {
min-height: 44px;
}
.icon-option svg {
width: 24px;
height: 24px;
}
.day-checkbox-label {
min-height: 44px;
padding: var(--space-1);
}
/* Mood and rating buttons */
.mood-btn {
min-width: 44px;
min-height: 44px;
font-size: 36px;
}
.rating-star {
font-size: 36px;
}
}
/* Habits grid */

View File

@@ -3,7 +3,7 @@
* Swipe left/right to navigate between pages
*/
(function() {
const pages = ['index.html', 'notes.html', 'files.html'];
const pages = ['index.html', 'notes.html', 'habits.html', 'files.html', 'workspace.html'];
// Get current page index
function getCurrentIndex() {

View File

@@ -8,6 +8,7 @@ Story US-010: Frontend - Check-in interaction (click and long-press)
Story US-011: Frontend - Skip, lives display, and delete confirmation
Story US-012: Frontend - Filter and sort controls
Story US-013: Frontend - Stats section and weekly summary
Story US-014: Frontend - Mobile responsive and touch optimization
"""
import sys
@@ -1466,6 +1467,138 @@ def test_typecheck_us013():
assert result == 0, "api.py should pass typecheck (syntax check)"
print("✓ Test 89: Typecheck passes")
def test_mobile_grid_responsive():
"""Test 90: Grid shows 1 column on screens below 768px"""
habits_path = Path(__file__).parent.parent / 'habits.html'
content = habits_path.read_text()
# Check for mobile breakpoint
assert '@media (max-width: 768px)' in content, "Should have mobile breakpoint"
assert 'grid-template-columns: 1fr' in content, "Should use 1 column on mobile"
print("✓ Test 90: Grid is responsive for mobile (1 column)")
def test_tablet_grid_responsive():
"""Test 91: Grid shows 2 columns between 768px and 1200px"""
habits_path = Path(__file__).parent.parent / 'habits.html'
content = habits_path.read_text()
# Check for tablet breakpoint
assert '@media (min-width: 769px) and (max-width: 1200px)' in content, "Should have tablet breakpoint"
assert 'repeat(2, 1fr)' in content, "Should use 2 columns on tablet"
print("✓ Test 91: Grid shows 2 columns on tablet screens")
def test_touch_targets_44px():
"""Test 92: All buttons and interactive elements have minimum 44px touch target"""
habits_path = Path(__file__).parent.parent / 'habits.html'
content = habits_path.read_text()
# Check for minimum touch target sizes in mobile styles
assert 'min-width: 44px' in content, "Should have min-width 44px for touch targets"
assert 'min-height: 44px' in content, "Should have min-height 44px for touch targets"
assert 'min-height: 48px' in content, "Check-in button should have min-height 48px"
print("✓ Test 92: Touch targets meet 44px minimum on mobile")
def test_modals_scrollable_mobile():
"""Test 93: Modals are scrollable with max-height 90vh on small screens"""
habits_path = Path(__file__).parent.parent / 'habits.html'
content = habits_path.read_text()
# Check modal has max-height and overflow
assert 'max-height: 90vh' in content, "Modal should have max-height 90vh"
assert 'overflow-y: auto' in content, "Modal should have overflow-y auto for scrolling"
print("✓ Test 93: Modals are scrollable on small screens")
def test_pickers_wrap_mobile():
"""Test 94: Icon and color pickers wrap properly on mobile"""
habits_path = Path(__file__).parent.parent / 'habits.html'
content = habits_path.read_text()
# Check for mobile grid adjustments in @media query
mobile_section = content[content.find('@media (max-width: 768px)'):content.find('@media (max-width: 768px)') + 3000]
assert 'grid-template-columns: repeat(4, 1fr)' in mobile_section, "Pickers should use 4 columns on mobile"
print("✓ Test 94: Icon and color pickers wrap properly on mobile")
def test_filter_bar_stacks_mobile():
"""Test 95: Filter bar stacks vertically on mobile"""
habits_path = Path(__file__).parent.parent / 'habits.html'
content = habits_path.read_text()
# Check for filter bar mobile styles
mobile_section = content[content.find('@media (max-width: 768px)'):content.find('@media (max-width: 768px)') + 3000]
assert '.filter-bar' in mobile_section, "Should have filter-bar mobile styles"
assert 'flex-direction: column' in mobile_section, "Filter bar should stack vertically"
print("✓ Test 95: Filter bar stacks vertically on mobile")
def test_stats_2x2_mobile():
"""Test 96: Stats row shows 2x2 grid on mobile"""
habits_path = Path(__file__).parent.parent / 'habits.html'
content = habits_path.read_text()
# Check for stats row mobile layout
mobile_section = content[content.find('@media (max-width: 768px)'):content.find('@media (max-width: 768px)') + 3000]
assert '.stats-row' in mobile_section, "Should have stats-row mobile styles"
assert 'grid-template-columns: repeat(2, 1fr)' in mobile_section, "Stats should use 2x2 grid on mobile"
print("✓ Test 96: Stats row shows 2x2 grid on mobile")
def test_swipe_nav_integration():
"""Test 97: swipe-nav.js is integrated for page navigation"""
habits_path = Path(__file__).parent.parent / 'habits.html'
content = habits_path.read_text()
# Check swipe-nav.js is included
assert 'src="/echo/swipe-nav.js"' in content, "Should include swipe-nav.js"
# Check swipe-nav.js has habits.html in pages array
swipe_nav_path = Path(__file__).parent.parent / 'swipe-nav.js'
if swipe_nav_path.exists():
swipe_content = swipe_nav_path.read_text()
assert 'habits.html' in swipe_content, "swipe-nav.js should include habits.html in pages array"
print("✓ Test 97: swipe-nav.js is integrated")
def test_mobile_form_inputs():
"""Test 98: Form inputs are touch-friendly on mobile"""
habits_path = Path(__file__).parent.parent / 'habits.html'
content = habits_path.read_text()
# Check for form input mobile styles
mobile_section = content[content.find('@media (max-width: 768px)'):content.find('@media (max-width: 768px)') + 3000]
assert '.form-input' in mobile_section or 'min-height: 44px' in mobile_section, "Form inputs should have mobile styles"
print("✓ Test 98: Form inputs are touch-friendly on mobile")
def test_mobile_no_console_errors():
"""Test 99: No obvious console error sources in mobile-specific code"""
habits_path = Path(__file__).parent.parent / 'habits.html'
content = habits_path.read_text()
# Basic sanity checks
assert '<style>' in content, "Should have style tags"
assert '@media' in content, "Should have media queries"
assert '</style>' in content, "Style tags should be closed"
print("✓ Test 99: No obvious console error sources")
def test_typecheck_us014():
"""Test 100: Typecheck passes after US-014 changes"""
api_path = Path(__file__).parent.parent / 'api.py'
result = os.system(f'python3 -m py_compile {api_path}')
assert result == 0, "api.py should pass typecheck"
print("✓ Test 100: Typecheck passes")
def test_mobile_day_checkboxes_wrap():
"""Test 101: Day checkboxes wrap properly on mobile"""
habits_path = Path(__file__).parent.parent / 'habits.html'
content = habits_path.read_text()
# Check for day checkboxes mobile styles
mobile_section = content[content.find('@media (max-width: 768px)'):content.find('@media (max-width: 768px)') + 3000]
assert '.day-checkboxes' in mobile_section, "Should have day-checkboxes mobile styles"
print("✓ Test 101: Day checkboxes wrap properly on mobile")
def run_all_tests():
"""Run all tests in sequence"""
tests = [
@@ -1566,9 +1699,22 @@ def run_all_tests():
test_stats_css_styling,
test_stats_no_console_errors,
test_typecheck_us013,
# US-014 tests
test_mobile_grid_responsive,
test_tablet_grid_responsive,
test_touch_targets_44px,
test_modals_scrollable_mobile,
test_pickers_wrap_mobile,
test_filter_bar_stacks_mobile,
test_stats_2x2_mobile,
test_swipe_nav_integration,
test_mobile_form_inputs,
test_mobile_no_console_errors,
test_typecheck_us014,
test_mobile_day_checkboxes_wrap,
]
print(f"\nRunning {len(tests)} frontend tests for US-006 through US-013...\n")
print(f"\nRunning {len(tests)} frontend tests for US-006 through US-014...\n")
failed = []
for test in tests: