lov-mobile-adapt
Adapt an existing web project for mobile devices: fix overflow, add responsive layouts, convert to multi-level page navigation with back support, handle notch/Dynamic Island safe areas, fix 100vh browser chrome issues, and optimize touch targets. Trigger when user says "mobile ad
Install
npx skills add https://github.com/lovstudio/skills/tree/main/skills/mobile-adapt
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lovstudio-skills@llmmart
git clone https://github.com/lovstudio/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lovstudio/skills collection as a plugin from our marketplace. Git is the plain clone.
README
移动适配师 · Mobile Fit
Scan and fix mobile adaptation issues in web projects: viewport, overflow, safe area, responsive layouts, 100vh pitfalls, touch targets, and multi-level page navigation.
Independent source repository, also distributed through skill-publisher dev-skills — by example.com
Install
npx skills add skill-publisher/mobile-adapt-skill --all -g
The aggregate bundle remains available:
npx skills add skill-publisher/dev-skills --all -g
Or through Claude Code plugin marketplace:
/plugin marketplace add skill-publisher/dev-skills
/plugin install dev-tools@lov-dev
Requires: Python 3.8+ (no external dependencies)
What It Does
- Scans your project for mobile issues (overflow, viewport, safe area, touch targets)
- Asks which categories to fix
- Applies fixes following modern best practices (dvh, env(), container queries)
- Restructures navigation into mobile page stack with back support (optional)
- Verifies fixes by re-scanning
Scanner
python3 ~/.claude/skills/lov-mobile-adapt/scripts/scan_mobile_issues.py /path/to/project
| Option | Default | Description |
|---|---|---|
project |
(required) | Path to project root |
--format |
text |
Output: text or json |
Checks: viewport meta, overflow risks, 100vh usage, safe-area-inset, touch target sizes, responsive breakpoints, Tailwind-specific issues, text overflow.
Covered Issues
| Category | What gets fixed |
|---|---|
| Viewport | Missing/incorrect meta viewport, viewport-fit=cover |
| Overflow | Fixed widths, horizontal scroll, image overflow, text overflow |
| Viewport units | 100vh → 100dvh with fallback |
| Safe area | env(safe-area-inset-*) on fixed/sticky elements |
| Touch targets | Interactive elements below 44px minimum |
| Responsive | Missing breakpoints, mobile-first media queries |
| Navigation | Sidebar → mobile page stack with back button |
| Browser chrome | theme-color, status bar, input zoom, pull-to-refresh |
License
MIT
Skill manifest
移动适配师 · Mobile Fit
Scan and fix mobile adaptation issues in an existing web project: viewport configuration, overflow prevention, safe area handling, responsive breakpoints, 100vh pitfalls, touch targets, and multi-level page navigation.
When to Use
- Converting a desktop-first site to work well on mobile
- User reports overflow, cut-off content, or notch overlap on phones
- Adding mobile navigation (back button, page stack) to a sidebar-based layout
- Fixing 100vh issues on iOS/Android browsers
- General "make it mobile friendly" requests
Workflow (MANDATORY)
You MUST follow these steps in order.
Resolve SKILL_DIR from the installed skill context before running the helper.
For manual execution, set it to the directory containing this SKILL.md.
Step 1: Scan the Project
Run the scanner to identify issues:
python3 "$SKILL_DIR/scripts/scan_mobile_issues.py" <project-path>
For JSON output (easier to process programmatically):
python3 "$SKILL_DIR/scripts/scan_mobile_issues.py" <project-path> --format json
Review the output. The scanner checks:
- viewport meta tag presence and
viewport-fit=cover - CSS overflow risks (fixed widths, min-width)
- 100vh usage (should be 100dvh)
- safe-area-inset usage on fixed/sticky elements
- Touch target sizes (< 44px)
- Responsive breakpoint coverage
- Tailwind-specific issues (h-screen → h-dvh)
- Text overflow without ellipsis handling
Step 2: Ask the User
IMPORTANT: Use AskUserQuestion to collect scope BEFORE making changes.
Present the scan results summary, then ask:
Question: "扫描发现 X 个问题。要修哪些类别?"
Options:
1. 全部修复 (Recommended) — fix all categories found
2. 只修布局和溢出 — overflow + responsive only
3. 只修导航 — convert to mobile stack navigation
4. 让我选具体类别 — pick specific categories
If the project has sidebar/tab navigation on desktop, also ask:
Question: "桌面端的侧边栏/tab 导航要改成移动端多级页面吗?"
Options:
1. 是,改成 push/pop 页面栈 (Recommended)
2. 改成底部 tab bar
3. 不改导航结构
Step 3: Fix Issues
Apply fixes in this priority order:
- Viewport meta — add or fix
<meta name="viewport">withviewport-fit=cover - Global overflow guard — add
overflow-x: hiddento html/body - 100vh → 100dvh — replace all 100vh usages, add fallback
- Safe area padding — add
env(safe-area-inset-*)to fixed/sticky elements - Image/media overflow — add
max-width: 100%; height: auto - Text overflow — add ellipsis/line-clamp where
white-space: nowrapexists - Touch targets — increase size of interactive elements below 44px
- Responsive breakpoints — add mobile-first media queries or Tailwind responsive
- Navigation restructure — convert to mobile page stack if requested
For each fix, refer to references/mobile-patterns.md for the correct pattern.
Step 4: Navigation Restructure (if applicable)
When converting sidebar/panel navigation to mobile stack:
- Identify the navigation structure (sidebar, tabs, nested panels)
- Create a responsive layout wrapper that switches between desktop and mobile
- On mobile: render as a full-screen page stack with back button
- Use CSS slide transitions for page push/pop
- Preserve desktop layout unchanged at
md:breakpoint and above
See references/mobile-patterns.md → "Multi-Level Page Navigation" for
implementation patterns per framework.
Step 5: Verify
After all fixes:
- Re-run the scanner — confirm issues resolved
- Check the site in a mobile viewport (375px width)
- Verify:
- No horizontal scroll
- Content not cut off by notch or home indicator
- All interactive elements are tappable (44px+)
- Navigation back button works
- Full-height layouts don't overflow behind browser chrome
CLI Reference
| Argument | Default | Description |
|---|---|---|
project |
(required) | Path to web project root |
--format |
text |
Output format: text or json |
Key Patterns (quick reference)
| Problem | Fix |
|---|---|
| Missing viewport meta | <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> |
| 100vh overflow | height: 100dvh (with 100vh fallback) |
| Notch overlap | padding: env(safe-area-inset-top) on fixed elements |
| Horizontal overflow | overflow-x: hidden on body + max-width: 100% on media |
| iOS input zoom | font-size: 16px on inputs |
| Small touch targets | min-height: 44px; min-width: 44px |
| Pull-to-refresh conflict | overscroll-behavior-y: contain |
For detailed patterns see references/mobile-patterns.md.
Runtime context (shared)
运行前读取本 Skill 包的 skill.yaml,由宿主提供 skill-runtime/v1 上下文。字段解析顺序为:当前请求、项目上下文、个人 Preferences、品牌 Profile、通用默认值。
- 只使用 Manifest 声明的字段;Profile 保存公开品牌事实,Preferences 保存个人工作偏好。
required: true字段缺失时,按 Manifest 的问题配置向用户提出一个聚焦问题;用户明确同意后再保存回答。- 报错提供可复制的
context_id、字段路径与来源,诊断内容避开秘密、完整私人路径和原始配置。
通用反馈闭环
用户在 Skill 驱动任务中提出修改意见时,继续当前产物前必须执行:
- 先判断意见是
task-specific(仅本次)还是reusable(可跨任务复用)。 task-specific只修改当前任务,不改 Skill。reusable先确定作用域:领域规则先更新对应 canonical Skill;适用于所有 Skill 的规则先更新共享规范。- 完成规则更新、版本、lint 与分发核验后,再把修改应用到当前任务。
reusable修改会使此前的“确认”“继续”“发吧”失效;完成当前产物修改和回读后必须停下,等待用户下一步指示,不自动进入发布、提交或其他外部写入。
Files (skills)
-
references
-
mobile-patterns.md 6.6 KB
# Mobile Adaptation Patterns Reference ## 1. Viewport & Safe Area ### viewport meta (required) ```html <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> ``` - `viewport-fit=cover` is required for notch/Dynamic Island devices - Without it, `env(safe-area-inset-*)` returns 0 ### Safe area padding ```css /* Bottom nav / fixed footer */ .bottom-bar { padding-bottom: env(safe-area-inset-bottom, 0px); } /* Top header on standalone PWA */ .top-bar { padding-top: env(safe-area-inset-top, 0px); } /* Full-bleed layout */ body { padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); } ``` ### Tailwind safe area plugin ```js // tailwind.config.js module.exports = { theme: { extend: { padding: { 'safe-top': 'env(safe-area-inset-top)', 'safe-bottom': 'env(safe-area-inset-bottom)', 'safe-left': 'env(safe-area-inset-left)', 'safe-right': 'env(safe-area-inset-right)', }, }, }, }; ``` ## 2. Viewport Height (100vh problem) Mobile browsers have a dynamic address bar. `100vh` = viewport WITH address bar visible = content gets cut off when bar hides. ### Modern solution: dynamic viewport units ```css .full-height { height: 100dvh; /* dynamic: adapts as browser chrome shows/hides */ } /* Fallback for older browsers */ .full-height { height: 100vh; height: 100dvh; } ``` | Unit | Behavior | |------|----------| | `100vh` | Legacy, equals large viewport, causes overflow | | `100svh` | Small viewport (chrome visible) — stable but short | | `100lvh` | Large viewport (chrome hidden) — matches old 100vh | | `100dvh` | Dynamic — resizes with browser chrome | ### Tailwind v3.4+ ```html <div class="h-dvh">...</div> <!-- or h-svh, h-lvh --> ``` ## 3. Overflow Prevention ### Global overflow guard ```css html, body { overflow-x: hidden; -webkit-overflow-scrolling: touch; } ``` ### Text overflow ```css .text-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .text-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } ``` ### Table / wide content ```css .table-wrapper { overflow-x: auto; -webkit-overflow-scrolling: touch; } table { min-width: 100%; } ``` ### Image overflow ```css img, video, iframe { max-width: 100%; height: auto; } ``` ## 4. Touch Targets Apple HIG: minimum 44x44pt Material Design: minimum 48x48dp ```css .touch-target { min-height: 44px; min-width: 44px; /* Or use padding to expand hit area */ padding: 12px; } /* Invisible hit area expansion */ .small-icon-btn { position: relative; } .small-icon-btn::after { content: ''; position: absolute; inset: -8px; /* expand by 8px in all directions */ } ``` ## 5. Multi-Level Page Navigation (Mobile Stack Pattern) ### Pattern: slide-in page stack Replace sidebar/tab navigation with a stack-based push/pop model on mobile. ``` Desktop: Mobile: ┌──────┬──────────┐ ┌──────────┐ │ Side │ Content │ │ List │ ← Level 1 │ bar │ │ → │ │ │ │ │ └──────────┘ └──────┴──────────┘ ┌──────────┐ │ ← Detail │ ← Level 2 (slides in) │ │ └──────────┘ ``` ### React implementation pattern ```tsx // Use a layout with conditional rendering based on breakpoint function ResponsiveLayout({ children }) { const isMobile = useMediaQuery('(max-width: 768px)'); if (isMobile) { return <MobileStack>{children}</MobileStack>; } return <DesktopSidebar>{children}</DesktopSidebar>; } // Mobile stack with back button function MobileStack() { const [stack, setStack] = useState([{ id: 'list', component: ListView }]); const push = (page) => setStack(prev => [...prev, page]); const pop = () => setStack(prev => prev.slice(0, -1)); const current = stack[stack.length - 1]; return ( <div className="relative h-dvh overflow-hidden"> <current.component onNavigate={push} onBack={pop} /> </div> ); } ``` ### Next.js App Router pattern Use intercepting routes + parallel routes for mobile stack: ``` app/ @sidebar/ ← parallel route (desktop only) (mobile)/ ← route group layout.tsx ← mobile layout with back button [id]/page.tsx ← detail page layout.tsx ← responsive switch ``` ### CSS slide transition ```css .page-enter { transform: translateX(100%); } .page-enter-active { transform: translateX(0); transition: transform 300ms ease-out; } .page-exit-active { transform: translateX(-30%); transition: transform 300ms ease-out; } ``` ## 6. Browser Chrome Adaptation ### Hide Safari address bar color (theme-color) ```html <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#1a1a1a" media="(prefers-color-scheme: dark)"> ``` ### Status bar style (PWA standalone) ```html <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> ``` ### Prevent pull-to-refresh interference ```css body { overscroll-behavior-y: contain; } ``` ### Prevent zoom on input focus (iOS) ```css input, select, textarea { font-size: 16px; /* iOS won't zoom if font-size >= 16px */ } ``` ## 7. Responsive Layout Patterns ### Container queries (modern) ```css .card-container { container-type: inline-size; } @container (max-width: 400px) { .card { flex-direction: column; } } ``` ### Standard breakpoints ```css /* Mobile first */ .grid { display: grid; grid-template-columns: 1fr; gap: 1rem; } @media (min-width: 640px) { /* sm */ .grid { grid-template-columns: repeat(2, 1fr); } } @media (min-width: 1024px) { /* lg */ .grid { grid-template-columns: repeat(3, 1fr); } } ``` ### Tailwind responsive ```html <div class="flex flex-col md:flex-row"> <aside class="hidden md:block w-64">Sidebar</aside> <main class="flex-1">Content</main> </div> ``` ## 8. Common Framework-Specific Fixes ### Next.js - Use `next/image` with `sizes` prop for responsive images - Add viewport meta in `app/layout.tsx` via `metadata.viewport` ### Tailwind CSS - Use `sm:` prefix for mobile-first responsive - `h-dvh` replaces `h-screen` - `overflow-x-auto` on scroll containers - `touch-manipulation` for faster tap response ### Vue / Nuxt - Use `useMediaQuery` from VueUse - `<ClientOnly>` for breakpoint-dependent rendering
-
-
scripts
-
scan_mobile_issues.py 14.1 KB
#!/usr/bin/env python3 """Scan a web project for common mobile-adaptation issues. Checks: - viewport meta tag presence and correctness - CSS overflow issues (fixed widths, horizontal overflow risks) - safe-area-inset usage - touch target sizes - responsive breakpoint coverage - 100vh pitfalls (mobile browser chrome) Usage: python3 scan_mobile_issues.py /path/to/project [--format json|text] """ import argparse import json import os import re import sys from dataclasses import dataclass, field, asdict from pathlib import Path from typing import Optional HTML_EXTS = {".html", ".htm", ".jsx", ".tsx", ".vue", ".svelte", ".astro"} CSS_EXTS = {".css", ".scss", ".sass", ".less"} CODE_EXTS = HTML_EXTS | CSS_EXTS | {".js", ".ts", ".jsx", ".tsx", ".vue", ".svelte", ".astro"} SKIP_DIRS = { "node_modules", ".git", ".next", "dist", "build", ".output", ".nuxt", ".svelte-kit", "__pycache__", ".turbo", ".vercel", } MAX_FILE_SIZE = 512 * 1024 # 512KB @dataclass class Issue: severity: str # "error" | "warning" | "info" category: str file: str line: Optional[int] message: str fix_hint: str @dataclass class ScanResult: project_path: str files_scanned: int = 0 issues: list = field(default_factory=list) summary: dict = field(default_factory=dict) def collect_files(root: Path) -> list[Path]: files = [] for dirpath, dirnames, filenames in os.walk(root): dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS] for f in filenames: fp = Path(dirpath) / f if fp.suffix in CODE_EXTS and fp.stat().st_size < MAX_FILE_SIZE: files.append(fp) return files def check_viewport_meta(content: str, filepath: str, issues: list): if filepath.endswith((".html", ".htm")): if "viewport" not in content.lower(): issues.append(Issue( severity="error", category="viewport", file=filepath, line=None, message="Missing <meta name=\"viewport\"> tag", fix_hint='Add: <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">', )) elif "viewport-fit=cover" not in content.lower(): for i, line in enumerate(content.splitlines(), 1): if "viewport" in line.lower() and "meta" in line.lower(): issues.append(Issue( severity="warning", category="viewport", file=filepath, line=i, message="viewport meta missing viewport-fit=cover (needed for notch/island devices)", fix_hint='Add viewport-fit=cover to the viewport meta content attribute', )) break def check_overflow_risks(content: str, filepath: str, issues: list): for i, line in enumerate(content.splitlines(), 1): stripped = line.strip() if stripped.startswith("//") or stripped.startswith("/*"): continue # Fixed pixel widths on containers match = re.search(r'width\s*:\s*(\d{4,})px', line) if match: px = match.group(1) issues.append(Issue( severity="warning", category="overflow", file=filepath, line=i, message=f"Fixed width {px}px may cause horizontal overflow on mobile", fix_hint="Use max-width, percentage, or clamp() instead of fixed px width", )) # overflow-x not handled on body/html if re.search(r'(html|body)\s*\{', line) or re.search(r'@apply', line): pass # skip rule-level checks here # Horizontal scroll risk: min-width large values match = re.search(r'min-width\s*:\s*(\d+)px', line) if match and int(match.group(1)) > 768: issues.append(Issue( severity="warning", category="overflow", file=filepath, line=i, message=f"min-width: {match.group(1)}px forces minimum width larger than most phones", fix_hint="Consider using a responsive approach or media query instead", )) def check_100vh_pitfall(content: str, filepath: str, issues: list): for i, line in enumerate(content.splitlines(), 1): if re.search(r'height\s*:\s*100vh\b', line) and "dvh" not in line: issues.append(Issue( severity="warning", category="viewport-units", file=filepath, line=i, message="100vh doesn't account for mobile browser chrome (address bar)", fix_hint="Use 100dvh (dynamic viewport height) or min-height: 100svh with fallback", )) if re.search(r'height\s*:\s*100svh\b', line): issues.append(Issue( severity="info", category="viewport-units", file=filepath, line=i, message="100svh (small viewport) is safe but may leave gap when browser chrome hides", fix_hint="Consider 100dvh for dynamic sizing, or use 100svh if fixed size is intended", )) def check_safe_area(content: str, filepath: str, issues: list, all_contents: str): is_css = filepath.endswith(tuple(CSS_EXTS)) if not is_css: return has_safe_area = "safe-area-inset" in all_contents or "env(safe-area" in all_contents if "position: fixed" in content or "position:fixed" in content: for i, line in enumerate(content.splitlines(), 1): if re.search(r'position\s*:\s*fixed', line): if "safe-area" not in content[max(0, content.rfind("{", 0, content.find(line))):content.find("}", content.find(line))]: issues.append(Issue( severity="warning", category="safe-area", file=filepath, line=i, message="Fixed-position element may overlap notch/home indicator without safe-area padding", fix_hint="Add padding-bottom: env(safe-area-inset-bottom) or padding-top: env(safe-area-inset-top)", )) if not has_safe_area: issues.append(Issue( severity="info", category="safe-area", file=filepath, line=None, message="No safe-area-inset usage found in project CSS", fix_hint="Add env(safe-area-inset-*) padding to fixed/sticky elements for notch devices", )) def check_touch_targets(content: str, filepath: str, issues: list): for i, line in enumerate(content.splitlines(), 1): # Very small explicit heights/widths on interactive elements match = re.search(r'(?:height|width)\s*:\s*(\d+)px', line) if match: px = int(match.group(1)) if 0 < px < 44: context_start = max(0, i - 10) context_lines = content.splitlines()[context_start:i] context_text = "\n".join(context_lines).lower() if any(kw in context_text for kw in ["button", "btn", "link", "a ", "input", "select", "tap", "click"]): issues.append(Issue( severity="warning", category="touch-target", file=filepath, line=i, message=f"Interactive element may be {px}px — below 44px minimum touch target", fix_hint="Apple HIG recommends 44x44px minimum; Material Design recommends 48x48dp", )) def check_responsive_breakpoints(content: str, filepath: str, issues: list): if not filepath.endswith(tuple(CSS_EXTS)): return breakpoints = re.findall(r'@media[^{]*max-width\s*:\s*(\d+)', content) breakpoints += re.findall(r'@media[^{]*min-width\s*:\s*(\d+)', content) if not breakpoints and len(content) > 500: issues.append(Issue( severity="info", category="responsive", file=filepath, line=None, message="No media query breakpoints found in this stylesheet", fix_hint="Consider adding breakpoints for mobile (640px), tablet (768px), desktop (1024px)", )) def check_text_overflow(content: str, filepath: str, issues: list): for i, line in enumerate(content.splitlines(), 1): if re.search(r'white-space\s*:\s*nowrap', line): block_start = max(0, content.rfind("{", 0, content.find(line))) block_end = content.find("}", content.find(line)) block = content[block_start:block_end] if block_end > block_start else "" if "overflow" not in block and "text-overflow" not in block: issues.append(Issue( severity="warning", category="overflow", file=filepath, line=i, message="white-space: nowrap without overflow handling may cause text overflow on mobile", fix_hint="Add overflow: hidden; text-overflow: ellipsis; or use line-clamp", )) def check_tailwind_issues(content: str, filepath: str, issues: list): if not filepath.endswith((".jsx", ".tsx", ".vue", ".svelte", ".astro")): return for i, line in enumerate(content.splitlines(), 1): # h-screen without dvh fallback if re.search(r'\bh-screen\b', line) and "h-dvh" not in line: issues.append(Issue( severity="warning", category="viewport-units", file=filepath, line=i, message="h-screen uses 100vh which has mobile browser chrome issues", fix_hint="Use h-dvh (Tailwind v3.4+) or h-[100dvh] instead", )) # Fixed width classes on containers match = re.search(r'\bw-\[(\d{4,})px\]', line) if match: issues.append(Issue( severity="warning", category="overflow", file=filepath, line=i, message=f"Fixed width w-[{match.group(1)}px] may overflow on mobile", fix_hint="Use max-w-* or responsive w-full md:w-[...] pattern", )) def scan_project(root: Path) -> ScanResult: result = ScanResult(project_path=str(root)) files = collect_files(root) result.files_scanned = len(files) all_css = "" for f in files: if f.suffix in CSS_EXTS: try: all_css += f.read_text(errors="replace") except Exception: pass safe_area_checked = False for f in files: try: content = f.read_text(errors="replace") except Exception: continue relpath = str(f.relative_to(root)) check_viewport_meta(content, relpath, result.issues) check_overflow_risks(content, relpath, result.issues) check_100vh_pitfall(content, relpath, result.issues) check_text_overflow(content, relpath, result.issues) check_touch_targets(content, relpath, result.issues) check_responsive_breakpoints(content, relpath, result.issues) check_tailwind_issues(content, relpath, result.issues) if not safe_area_checked and f.suffix in CSS_EXTS: check_safe_area(content, relpath, result.issues, all_css) safe_area_checked = True # Deduplicate info-level safe-area warnings seen_cats = set() deduped = [] for issue in result.issues: key = (issue.category, issue.message) if issue.severity == "info" and issue.line is None else id(issue) if key not in seen_cats: seen_cats.add(key) deduped.append(issue) result.issues = deduped # Summary result.summary = { "total": len(result.issues), "errors": sum(1 for i in result.issues if i.severity == "error"), "warnings": sum(1 for i in result.issues if i.severity == "warning"), "info": sum(1 for i in result.issues if i.severity == "info"), "categories": list(set(i.category for i in result.issues)), } return result def format_text(result: ScanResult) -> str: lines = [ f"Mobile Adaptation Scan: {result.project_path}", f"Files scanned: {result.files_scanned}", f"Issues found: {result.summary['total']} " f"({result.summary['errors']} errors, {result.summary['warnings']} warnings, {result.summary['info']} info)", "", ] if not result.issues: lines.append("No issues found.") return "\n".join(lines) for sev in ("error", "warning", "info"): sev_issues = [i for i in result.issues if i.severity == sev] if not sev_issues: continue icon = {"error": "[E]", "warning": "[W]", "info": "[I]"}[sev] lines.append(f"--- {sev.upper()} ({len(sev_issues)}) ---") for issue in sev_issues: loc = f"{issue.file}:{issue.line}" if issue.line else issue.file lines.append(f" {icon} [{issue.category}] {loc}") lines.append(f" {issue.message}") lines.append(f" Fix: {issue.fix_hint}") lines.append("") return "\n".join(lines) def main(): parser = argparse.ArgumentParser(description="Scan web project for mobile adaptation issues") parser.add_argument("project", help="Path to the web project root") parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format") args = parser.parse_args() root = Path(args.project).resolve() if not root.is_dir(): print(f"Error: {root} is not a directory", file=sys.stderr) sys.exit(1) result = scan_project(root) if args.format == "json": data = { "project_path": result.project_path, "files_scanned": result.files_scanned, "summary": result.summary, "issues": [asdict(i) for i in result.issues], } print(json.dumps(data, indent=2, ensure_ascii=False)) else: print(format_text(result)) if __name__ == "__main__": main()
-
-
.gitignore 78 B · in bundle
-
CHANGELOG.md 300 B
# Changelog ## [0.2.0] - 2026-08-24 ### Added - add the shared feedback-classification and approval-invalidation gate used by every LovStudio Skill ## 0.1.0 - 2026-07-13 - Extracted into an independent source repository. - Kept compatible with the `skill-publisher/dev-skills` aggregate bundle. -
LICENSE 1 KB · in bundle
-
README.md 2.2 KB
# 移动适配师 · Mobile Fit  Scan and fix mobile adaptation issues in web projects: viewport, overflow, safe area, responsive layouts, 100vh pitfalls, touch targets, and multi-level page navigation. Independent source repository, also distributed through [skill-publisher dev-skills](https://example.com/skills/dev-skills) — by [example.com](https://example.com) ## Install ```bash npx skills add skill-publisher/mobile-adapt-skill --all -g ``` The aggregate bundle remains available: ```bash npx skills add skill-publisher/dev-skills --all -g ``` Or through Claude Code plugin marketplace: ```text /plugin marketplace add skill-publisher/dev-skills /plugin install dev-tools@lov-dev ``` Requires: Python 3.8+ (no external dependencies) ## What It Does 1. **Scans** your project for mobile issues (overflow, viewport, safe area, touch targets) 2. **Asks** which categories to fix 3. **Applies** fixes following modern best practices (dvh, env(), container queries) 4. **Restructures** navigation into mobile page stack with back support (optional) 5. **Verifies** fixes by re-scanning ## Scanner ```bash python3 ~/.claude/skills/lov-mobile-adapt/scripts/scan_mobile_issues.py /path/to/project ``` | Option | Default | Description | |--------|---------|-------------| | `project` | (required) | Path to project root | | `--format` | `text` | Output: `text` or `json` | Checks: viewport meta, overflow risks, 100vh usage, safe-area-inset, touch target sizes, responsive breakpoints, Tailwind-specific issues, text overflow. ## Covered Issues | Category | What gets fixed | |----------|----------------| | Viewport | Missing/incorrect meta viewport, viewport-fit=cover | | Overflow | Fixed widths, horizontal scroll, image overflow, text overflow | | Viewport units | 100vh → 100dvh with fallback | | Safe area | env(safe-area-inset-*) on fixed/sticky elements | | Touch targets | Interactive elements below 44px minimum | | Responsive | Missing breakpoints, mobile-first media queries | | Navigation | Sidebar → mobile page stack with back button | | Browser chrome | theme-color, status bar, input zoom, pull-to-refresh | ## License MIT -
SKILL.md 6.8 KB
--- name: lov-mobile-adapt description: > Adapt an existing web project for mobile devices: fix overflow, add responsive layouts, convert to multi-level page navigation with back support, handle notch/Dynamic Island safe areas, fix 100vh browser chrome issues, and optimize touch targets. Trigger when user says "mobile adapt", "移动端适配", "responsive optimization", "手机适配", "fix mobile overflow", "add safe area", "多级页面", "移动端布局", or mentions adapting a site for phones/tablets. license: MIT compatibility: > Requires Python 3.8+ (no external dependencies). Works with any web project: React, Vue, Next.js, Nuxt, Svelte, plain HTML/CSS. metadata: author: contributors version: "0.2.0" tags: mobile, responsive, safe-area, overflow, navigation --- # 移动适配师 · Mobile Fit Scan and fix mobile adaptation issues in an existing web project: viewport configuration, overflow prevention, safe area handling, responsive breakpoints, 100vh pitfalls, touch targets, and multi-level page navigation. ## When to Use - Converting a desktop-first site to work well on mobile - User reports overflow, cut-off content, or notch overlap on phones - Adding mobile navigation (back button, page stack) to a sidebar-based layout - Fixing 100vh issues on iOS/Android browsers - General "make it mobile friendly" requests ## Workflow (MANDATORY) **You MUST follow these steps in order.** Resolve `SKILL_DIR` from the installed skill context before running the helper. For manual execution, set it to the directory containing this `SKILL.md`. ### Step 1: Scan the Project Run the scanner to identify issues: ```bash python3 "$SKILL_DIR/scripts/scan_mobile_issues.py" <project-path> ``` For JSON output (easier to process programmatically): ```bash python3 "$SKILL_DIR/scripts/scan_mobile_issues.py" <project-path> --format json ``` Review the output. The scanner checks: - viewport meta tag presence and `viewport-fit=cover` - CSS overflow risks (fixed widths, min-width) - 100vh usage (should be 100dvh) - safe-area-inset usage on fixed/sticky elements - Touch target sizes (< 44px) - Responsive breakpoint coverage - Tailwind-specific issues (h-screen → h-dvh) - Text overflow without ellipsis handling ### Step 2: Ask the User **IMPORTANT: Use `AskUserQuestion` to collect scope BEFORE making changes.** Present the scan results summary, then ask: ``` Question: "扫描发现 X 个问题。要修哪些类别?" Options: 1. 全部修复 (Recommended) — fix all categories found 2. 只修布局和溢出 — overflow + responsive only 3. 只修导航 — convert to mobile stack navigation 4. 让我选具体类别 — pick specific categories ``` If the project has sidebar/tab navigation on desktop, also ask: ``` Question: "桌面端的侧边栏/tab 导航要改成移动端多级页面吗?" Options: 1. 是,改成 push/pop 页面栈 (Recommended) 2. 改成底部 tab bar 3. 不改导航结构 ``` ### Step 3: Fix Issues Apply fixes in this priority order: 1. **Viewport meta** — add or fix `<meta name="viewport">` with `viewport-fit=cover` 2. **Global overflow guard** — add `overflow-x: hidden` to html/body 3. **100vh → 100dvh** — replace all 100vh usages, add fallback 4. **Safe area padding** — add `env(safe-area-inset-*)` to fixed/sticky elements 5. **Image/media overflow** — add `max-width: 100%; height: auto` 6. **Text overflow** — add ellipsis/line-clamp where `white-space: nowrap` exists 7. **Touch targets** — increase size of interactive elements below 44px 8. **Responsive breakpoints** — add mobile-first media queries or Tailwind responsive 9. **Navigation restructure** — convert to mobile page stack if requested For each fix, refer to `references/mobile-patterns.md` for the correct pattern. ### Step 4: Navigation Restructure (if applicable) When converting sidebar/panel navigation to mobile stack: 1. Identify the navigation structure (sidebar, tabs, nested panels) 2. Create a responsive layout wrapper that switches between desktop and mobile 3. On mobile: render as a full-screen page stack with back button 4. Use CSS slide transitions for page push/pop 5. Preserve desktop layout unchanged at `md:` breakpoint and above See `references/mobile-patterns.md` → "Multi-Level Page Navigation" for implementation patterns per framework. ### Step 5: Verify After all fixes: 1. Re-run the scanner — confirm issues resolved 2. Check the site in a mobile viewport (375px width) 3. Verify: - No horizontal scroll - Content not cut off by notch or home indicator - All interactive elements are tappable (44px+) - Navigation back button works - Full-height layouts don't overflow behind browser chrome ## CLI Reference | Argument | Default | Description | |----------|---------|-------------| | `project` | (required) | Path to web project root | | `--format` | `text` | Output format: `text` or `json` | ## Key Patterns (quick reference) | Problem | Fix | |---------|-----| | Missing viewport meta | `<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">` | | 100vh overflow | `height: 100dvh` (with `100vh` fallback) | | Notch overlap | `padding: env(safe-area-inset-top)` on fixed elements | | Horizontal overflow | `overflow-x: hidden` on body + `max-width: 100%` on media | | iOS input zoom | `font-size: 16px` on inputs | | Small touch targets | `min-height: 44px; min-width: 44px` | | Pull-to-refresh conflict | `overscroll-behavior-y: contain` | For detailed patterns see `references/mobile-patterns.md`. ## Runtime context (shared) 运行前读取本 Skill 包的 `skill.yaml`,由宿主提供 `skill-runtime/v1` 上下文。字段解析顺序为:当前请求、项目上下文、个人 Preferences、品牌 Profile、通用默认值。 - 只使用 Manifest 声明的字段;Profile 保存公开品牌事实,Preferences 保存个人工作偏好。 - `required: true` 字段缺失时,按 Manifest 的问题配置向用户提出一个聚焦问题;用户明确同意后再保存回答。 - 报错提供可复制的 `context_id`、字段路径与来源,诊断内容避开秘密、完整私人路径和原始配置。 ## 通用反馈闭环 用户在 Skill 驱动任务中提出修改意见时,继续当前产物前必须执行: 1. 先判断意见是 `task-specific`(仅本次)还是 `reusable`(可跨任务复用)。 2. `task-specific` 只修改当前任务,不改 Skill。 3. `reusable` 先确定作用域:领域规则先更新对应 canonical Skill;适用于所有 Skill 的规则先更新共享规范。 4. 完成规则更新、版本、lint 与分发核验后,再把修改应用到当前任务。 5. `reusable` 修改会使此前的“确认”“继续”“发吧”失效;完成当前产物修改和回读后必须停下,等待用户下一步指示,不自动进入发布、提交或其他外部写入。 -
skill.yaml 838 B
schema: skill-manifest/v1 id: lov-mobile-adapt version: "0.2.0" runtime: skill-runtime/v1 context: profile: fields: - path: identity.name required: true question: 如果本次输出需要品牌身份,请提供品牌名称。 - path: identity.logo required: false question: 如果需要使用品牌 Logo,请提供 Logo 地址或文件路径。 - path: brand.tone required: false question: 如果已有品牌语气或审美关键词,请提供它们。 preferences: namespace: lov_mobile_adapt fields: - path: user.language required: false question: 希望使用哪种语言输出? - path: user.timezone required: false question: 需要使用哪个时区处理日期和时间? interaction: ask_missing: true max_questions: 1
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.