feat: US-014 - Frontend - Mobile responsive and touch optimization
This commit is contained in:
@@ -86,6 +86,96 @@
|
|||||||
.filter-group {
|
.filter-group {
|
||||||
width: 100%;
|
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 */
|
/* Habits grid */
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Swipe left/right to navigate between pages
|
* Swipe left/right to navigate between pages
|
||||||
*/
|
*/
|
||||||
(function() {
|
(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
|
// Get current page index
|
||||||
function getCurrentIndex() {
|
function getCurrentIndex() {
|
||||||
|
|||||||
@@ -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-011: Frontend - Skip, lives display, and delete confirmation
|
||||||
Story US-012: Frontend - Filter and sort controls
|
Story US-012: Frontend - Filter and sort controls
|
||||||
Story US-013: Frontend - Stats section and weekly summary
|
Story US-013: Frontend - Stats section and weekly summary
|
||||||
|
Story US-014: Frontend - Mobile responsive and touch optimization
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -1466,6 +1467,138 @@ def test_typecheck_us013():
|
|||||||
assert result == 0, "api.py should pass typecheck (syntax check)"
|
assert result == 0, "api.py should pass typecheck (syntax check)"
|
||||||
print("✓ Test 89: Typecheck passes")
|
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():
|
def run_all_tests():
|
||||||
"""Run all tests in sequence"""
|
"""Run all tests in sequence"""
|
||||||
tests = [
|
tests = [
|
||||||
@@ -1566,9 +1699,22 @@ def run_all_tests():
|
|||||||
test_stats_css_styling,
|
test_stats_css_styling,
|
||||||
test_stats_no_console_errors,
|
test_stats_no_console_errors,
|
||||||
test_typecheck_us013,
|
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 = []
|
failed = []
|
||||||
for test in tests:
|
for test in tests:
|
||||||
|
|||||||
Reference in New Issue
Block a user