Patterns for debugging mobile-specific issues on iOS Safari and Android Chrome. Use this skill when encountering viewport, keyboard, or touch-related bugs that only reproduce on real mobile devices.
From product-playbook-for-agentic-codingnpx claudepluginhub daviswhitehead/product-playbook-for-agentic-coding-plugin --plugin product-playbook-for-agentic-codingThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
This skill provides patterns for debugging mobile-specific issues, particularly viewport and keyboard handling differences between iOS Safari and Android Chrome.
Use this skill when:
Many mobile bugs cannot be reproduced in:
Always test on real physical devices for:
iOS Safari and Android Chrome handle viewports completely differently:
| Behavior | Android Chrome | iOS Safari |
|---|---|---|
interactiveWidget: resizes-content | ✅ Resizes viewport | ❌ Ignored |
100dvh on keyboard open | ✅ Shrinks correctly | ⚠️ Layout viewport unchanged |
scrollIntoView() with keyboard | ✅ Works immediately | ❌ Needs 350ms delay |
| Keyboard dismiss detection | Via resize event | Via focusout/blur |
| Overscroll prevention | overscroll-behavior works | May need additional handling |
Key Implication: CSS-only solutions (dvh units, viewport meta) work on Android but require JavaScript workarounds on iOS Safari.
Symptom: When focusing an input, the keyboard covers it instead of scrolling into view.
Root Cause: iOS Safari doesn't resize the layout viewport when the keyboard appears.
Solution: Add scrollIntoView on focus with delay:
const handleFocus = () => {
// Wait for iOS keyboard animation (~300ms)
setTimeout(() => {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 350);
};
Apply to: All inputs that could be near the bottom of the screen (chat inputs, login forms, comment boxes).
Symptom: User can scroll past content to reveal gray/white space below.
Root Cause: Usually a wrapper element with min-h-screen or min-height: 100vh that extends beyond the viewport.
Debugging Steps:
layout.tsx or root layout for wrapper divsmin-h-screen, min-h-full, or min-height propertiesoverflow: auto instead of overflow: hiddenSolution:
/* globals.css */
html, body {
height: 100%;
overflow: hidden;
overscroll-behavior: none;
}
And: Remove wrapper divs with min-h-screen. Let pages handle their own height with h-dvh.
Symptom: Components don't fill available vertical space properly.
Root Cause: Broken flex layout cascade - using h-full instead of flex-1.
Solution: Ensure flex layout cascades through component tree:
// ❌ Wrong
<View className="flex-1">
<ChildComponent className="h-full" /> {/* Won't fill properly */}
</View>
// ✅ Correct
<View className="flex-1 flex flex-col">
<ChildComponent className="flex-1" /> {/* Fills remaining space */}
</View>
Symptom: After deleting all text (select all + delete), textarea stays expanded.
Root Cause: scrollHeight doesn't immediately reflect empty content.
Solution: Explicitly check for empty value:
const adjustHeight = () => {
if (!value || value.length === 0) {
element.style.height = `${minHeight}px`;
return;
}
// ... normal scrollHeight calculation
};
Symptom: Page bounces when scrolling past content edges.
Solution:
body {
overscroll-behavior: none;
}
For scroll containers:
<ScrollView
bounces={false} // iOS
overScrollMode="never" // Android
/>
When encountering a mobile-specific bug:
layout.tsxmin-h-screen wrappersoverflow: hidden on html/bodyinteractiveWidget settingflex-1 not h-full)scrollIntoView with 350ms delayinteractiveWidget: resizes-contentoverscroll-behavior: none// layout.tsx
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
maximumScale: 1, // Prevents zoom on input focus
// Android: Make keyboard resize viewport instead of overlay
interactiveWidget: 'resizes-content',
};
If your mobile fix uses requestAnimationFrame, tests may fail because Jest doesn't execute rAF callbacks synchronously.
Solution: Mock rAF in tests:
beforeEach(() => {
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => {
cb(0);
return 0;
});
});
afterEach(() => {
jest.restoreAllMocks();
});
Mobile debugging requires:
scrollIntoView with delaysThis skill works with:
/playbook:debug - Use this skill during mobile debugging sessions/playbook:learnings - Capture mobile-specific fixes for future referencedebugging-agent - Provides mobile-specific debugging patternsWhen in doubt: check on a real device, check the root layout, and remember iOS Safari needs special handling.