懶人包
- mobile-native 是 Emil Kowalski 收在
emilkowalski/skills(MIT)裡的 AI agent skill,專門處理「網頁在手機上像網站、不像 App」的 11 種症狀,2026-09-15 加入 repo。- repo 在 2026-10-21 是 48,160 stars,但這是 15 支 skill 共用的數字,不是 mobile-native 一支的。
- 我用 Claude Code(Sonnet)點名
/mobile-native審自己部落格的修前版本:19 列裡 14 列有問題,包含 12px 搜尋框會讓 iOS 自動放大、選單用100vh、沒有viewport-fit=cover。一次約 108 秒、0.43 美元。- 修完再審一次剩 10 項,其中 1 項是修法本身帶出來的新坑:底部導覽加了安全區,
body的留白卻沒跟著加。- Playwright 驗證線上版:觸控模擬下搜尋框 16px、滑鼠視窗同樣 390px 寬仍是 12px,選單高度剛好等於可視高度。iOS 實機行為我沒有驗證,模擬器證明不了安全區和網址列。
- mobile-native skill 是什麼?
- 它什麼時候會被觸發?
- 11 種症狀各在講什麼?
- 怎麼安裝和點名?
- 修前審查:它在我的部落格找到什麼?
- 怎麼修:commit 07d7267 的三段 diff
- 線上驗證:手機寬度量到什麼?
- 修後再審:還剩哪些問題?
- 有哪些限制和坑?
- 適合誰、不適合誰?
- 延伸資源
📱 mobile-native skill 是什麼?
mobile-native 是一份 311 行的 SKILL.md,給 Claude Code 這類 AI coding agent 讀。它自稱是「修東西的 skill」,只做一件事:把網頁在手機上露餡的地方一個一個拿掉。
它管的是平台層,也就是 viewport、觸控、捲動、安全區和瀏覽器外框。動畫怎麼設計交給同 repo 的 animate,動畫審查交給 review-animations,React Native 交給 animate-expo,這些它都不碰。
整份文件的核心論點有兩個,我覺得很準:
- 手機上「感覺卡卡的」多半不是動畫問題,而是點擊延遲、點下去閃一塊灰色、或 hover 狀態黏住不放。
- 桌機 Chrome 的裝置模擬不是手機。文件裡列的問題幾乎都不會在模擬器重現,只在模擬器測就會全部上線。
🎯 它什麼時候會被觸發?
frontmatter 只有 name 和 description 兩個欄位,沒有 disable-model-invocation,所以模型會依描述自己決定要不要載入。描述寫的觸發情境有:
- 網頁 App 在為手機開發、或在手機上被審查時
- 「在 Chrome 正常、在我手機上怪怪的」
- 做 PWA、bottom sheet、輪播、全螢幕版面或任何觸控互動時
另外文件裡有一段「初次回應」規則:沒帶具體問題就叫它時,它只會回一句固定的開場白,什麼都不做。所以點名時記得把要它做的事寫在同一句。
📋 11 種症狀各在講什麼?
文件的主體是一張症狀對照表,每一列再展開成一節講原因和程式碼。我依性質分成五組轉述:
| 分組 | 症狀 | 它給的修法重點 |
|---|---|---|
| 觸控回饋 | hover 點完黏住 | 所有 :hover 包進 @media (hover: hover) and (pointer: fine) |
| 觸控回饋 | 點擊時閃灰框或藍框 | 關掉 -webkit-tap-highlight-color |
| 觸控回饋 | 點下去反應慢 | 在按下那一刻就給回饋(:active),加 touch-action: manipulation |
| 觸控回饋 | 長按會選到按鈕文字 | 只在控制元件上加 user-select: none,不要加在 body |
| 尺寸與版面 | 高度不對 | App 外殼用 100dvh,首屏用 100svh |
| 尺寸與版面 | 點輸入框整頁放大 | 輸入框字級至少 16px |
| 尺寸與版面 | 內容停在瀏海外面 | viewport-fit=cover 搭配 env(safe-area-inset-*) |
| 捲動與手勢 | 下拉重新整理搶走捲動 | overscroll-behavior |
| 捲動與手勢 | 輪播被拖成垂直捲動 | 手勢區加 touch-action: pan-y |
| 瀏覽器外框 | 狀態列顏色對不上 | 每個色彩配置各一條 theme-color |
| 測試 | 在 Chrome 對、在手機錯 | 上實機測 |
- 5 條硬規則:每個修法都要附理由;用媒體查詢判斷能力,不准用 user agent 或螢幕寬度猜是不是觸控;觸控和滑鼠會同時存在(iPad 接觸控板、觸控筆電);絕對不准禁止縮放;沒上實機不算修好。
- Baseline:新專案第一個元件之前就該放的底線設定,包括
interactive-widget=resizes-content(讓 Android 鍵盤彈出時縮小版面)和-webkit-text-size-adjust: 100%(橫向時不自動放大字)。 - Never Ship 表:12 條上線前自檢,例如
100dvh不要用在行銷首屏(捲動時會跳版)、env()沒配viewport-fit=cover數值永遠是 0、從裝置模擬判定「修好了」也在禁止之列。
⚙️ 怎麼安裝和點名?
只裝這一支:
npx skills@latest add emilkowalski/skills --skill mobile-native
我在 2026-10-21 用 skills CLI 1.7.4 跑過,加 --agent claude-code --copy -y 會跳過互動選單,直接把 SKILL.md 複製到專案的 .claude/skills/mobile-native/,並產生一份 skills-lock.json 記下來源與雜湊值。安裝過程會顯示三家安全掃描的結果,這支是 Safe/0 alerts/Low Risk。
點名就是在需求前面打 /mobile-native。我的審查指令是這樣開頭的:
/mobile-native 只審查、不要修改任何檔案。審查目前資料夾(Astro + Tailwind v4 部落格的 src/)在手機瀏覽器上的問題。
注意文件的預設行為是「直接修」,它的 Output 段第一句就是套用修法。只想要報告的話,要像上面那樣明講不准改檔。
🔍 修前審查:它在我的部落格找到什麼?
我把部落格修正前(commit 07d7267 的上一版)的 src/ 複製到一個乾淨資料夾,裝好 skill,用 claude -p 加 Sonnet 跑上面那段指令,並要求每一列標明「看程式碼就能確定」還是「需要實機」。
結果:約 108 秒、0.43 美元、20 輪,用了 Glob 1 次、Grep 10 次、Read 8 次。產出 19 列,其中 14 列有問題、3 列確認沒問題、2 列是實機測試與 Baseline 的補充。
有一點要照實說:這次輸出的紀錄裡看不到 skill 全文被展開的痕跡,我判斷它有生效的依據是輸出逐字使用了 skill 症狀表的英文名稱,以及它提到只有 Baseline 段才寫的 interactive-widget 和 -webkit-text-size-adjust。
本文要修的三項就是從這份清單來的:
| 症狀 | 它指出的位置 | 證據 |
|---|---|---|
| 點輸入框整頁放大 | BlogFilters.tsx:176 與會員登入、留言板等 5 個元件 | 搜尋框是 text-xs(12px),其他輸入框是 text-sm(14px),都低於 16px |
| 高度不對 | NavbarNeub.astro:319 | 手機選單面板寫死 height: calc(100vh - 60px) |
| 內容停在瀏海外面 | RootLayout.astro:112、NavbarNeub.astro:357 | viewport 沒有 viewport-fit=cover,全站沒有任何 env(safe-area-inset-*),底部導覽沒有底部留白 |
:hover 都沒包媒體查詢、沒有關掉點擊高亮、沒有 touch-action、選單面板缺 overscroll-behavior、控制元件長按會選到字、沒有 theme-color。
它也有判斷「不用改」的地方,而且判斷對了。Tailwind v4 的 hover: 類別本身就會編譯成包在 @media (hover: hover) 裡的規則,它把這列標成未發現;viewport 沒有 user-scalable=no,它也明講保持現狀。
🛠️ 怎麼修:commit 07d7267 的三段 diff
部落格的 commit 07d7267(2026-10-21)改了 4 個檔案。和這三個症狀有關的片段如下。
一、輸入框 16px(iOS 點擊放大),global.css:
+@media (pointer: coarse) {
+ input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select {
+ font-size: max(16px, 1em);
+ }
+}
用 (pointer: coarse) 而不是 max-width,是照 skill 的硬規則「依能力判斷,不依寬度判斷」:桌機上把視窗縮窄,搜尋框維持原本 12px 的設計;只有主要指標是手指的裝置才拉到 16px。max(16px, 1em) 保留了原本就比 16px 大的輸入框。checkbox、radio、range 沒有文字可放大,所以排除。
二、選單 100vh 改成 dvh,NavbarNeub.astro:
-<div class="mobile-menu-panel fixed ... overflow-y-auto" style="top: 60px; height: calc(100vh - 60px);">
+<div class="mobile-menu-panel fixed ... overflow-y-auto" style="top: 60px;">
+ .mobile-menu-panel {
+ height: calc(100vh - 60px);
+ height: calc(100dvh - 60px);
高度從行內 style 搬到 <style> 區塊,寫兩行:舊瀏覽器看不懂 dvh 會用第一行的 100vh,新瀏覽器用第二行。手機網址列展開時,100vh 是網址列收起來的高度,面板底部會超出可視範圍;dvh 會跟著網址列伸縮。選單是貼著視窗的外殼元件,所以用 dvh 而不是 svh,這和 skill 的分法一致。
三、viewport-fit=cover 加 safe-area-inset-bottom,RootLayout.astro 和 NavbarNeub.astro:
-<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
+<meta name="theme-color" content="#FFFFFF" />
- class="mobile-bottom-nav md:hidden fixed bottom-0 ... grid grid-cols-5"
+ class="mobile-bottom-nav md:hidden fixed bottom-0 ... grid grid-cols-5 pb-[env(safe-area-inset-bottom)]"
兩步缺一不可:viewport-fit=cover 讓頁面畫到瀏海和 Home 指示條底下,env(safe-area-inset-bottom) 再把底部導覽的內容推回安全範圍。只加後者、沒加 meta,env() 的值永遠是 0。
同一個 commit 也把 .lift:hover 包進 (hover: hover) and (pointer: fine),並補了 80ms 的 .lift:active 按下回饋,對應「hover 黏住」和「點下去反應慢」兩列。
🧪 線上驗證:手機寬度量到什麼?
修完上線後,我用 Playwright 對 https://yanchen.app/blog/ 開兩種瀏覽環境,讀瀏覽器實際算出來的值:
| 項目 | 觸控模擬(iPhone 13 裝置設定) | 滑鼠視窗(390×844) |
|---|---|---|
pointer: coarse | 是 | 否 |
| 搜尋框 computed font-size | 16px | 12px |
| 搜尋框 class | 仍是 text-xs | 仍是 text-xs |
| viewport meta | width=device-width, initial-scale=1.0, viewport-fit=cover | 同左 |
| theme-color | #FFFFFF | 同左 |
| 選單面板高度 | 604px(top 60、bottom 664,等於可視高度 664) | 未量(桌機不顯示) |
打開漢堡選單後,面板從 60px 一路延伸到可視區底部:
這些數字能證明的是:CSS 和 meta 已經上線、規則有在觸控環境生效。不能證明的部分我留到限制那節講。
🔁 修後再審:還剩哪些問題?
我對修後版本跑了同一段指令:約 113 秒、0.48 美元、22 輪。結果是 10 項問題、2 項未發現、1 列實機提醒。
三個修法都被它認可:輸入框那列變成「未發現」,並點名 pointer: coarse 規則已涵蓋 12px 的搜尋框;選單那兩行 100vh/100dvh 被判定是正確寫法。
剩下的 10 項大多是修前就有、這次沒處理的:
- 其他
:hover(文章內連結、程式碼複製鈕、主題切換、卡片封面)沒包媒體查詢 - 沒有關掉點擊高亮、沒有
touch-action: manipulation - 底部導覽只有
hover:,觸控時按下去沒有任何回饋 - 選單面板和留言列表缺
overscroll-behavior: contain - 控制元件沒有
user-select: none - 只有一條
theme-color,切到 anti 主題時導覽列變米色、狀態列卻還是白色 - 一個主題和 404 頁還在用
100vh/min-h-screen
NavbarNeub.astro:400的body { padding-bottom: 64px }沒有加安全區。底部導覽現在是 64px 加上安全區高度,頁面底部卻只留 64px,頁尾最後一段會被導覽列蓋住。
我回頭 grep 確認它說的是對的。這是修法帶出來的副作用:導覽列變高了,替它讓位的留白沒跟著變。建議修法是 padding-bottom: calc(64px + env(safe-area-inset-bottom, 0px))。這條目前還沒修。
另外它也提醒橫向時左右安全區沒處理,兩條導覽列都只加了 bottom。
⚠️ 有哪些限制和坑?
一、模擬器量不到它最在意的那幾項。 Playwright 用的 Chromium 沒有網址列也沒有瀏海,env(safe-area-inset-bottom) 量到 0px,100vh 和 100dvh 算出來一樣,所以表格裡「604px 等於可視高度」不能證明 dvh 的效果。iOS 點輸入框會不會放大、Home 指示條有沒有壓到導覽列、網址列收合時選單會不會被切,我都沒有在實機驗證。這正是 skill 自己 Never Ship 表裡寫的「不要從裝置模擬判定修好了」。
二、選單底部和底部導覽重疊。 截圖裡選單面板延伸到可視區最底,底部導覽列疊在它上面(兩者在同一層)。目前 7 個項目剛好放得下,面板最後約 56px 是空白;如果之後選單再多一項,最後一項捲到底時會落在導覽列後面。
三、pointer: coarse 不是「是不是手機」。 它問的是主要指標裝置。觸控筆電如果主要指標是觸控板,就會拿到 12px;平板接滑鼠也一樣。這是 skill 刻意的設計,但要知道修法涵蓋的範圍是「主要用手指點的裝置」。
四、不要照表全收。 症狀表寫「下拉重整」的修法是在 html, body 加 overscroll-behavior: none,但部落格是文件型網站,使用者會想下拉重新整理。審查結果自己也說了根節點不必設 none,只在選單面板這種內部捲動區加 contain。文件裡其實也有寫「捲動型文件可以拿掉這行」,只是在 Baseline 段,不在症狀表。
五、theme-color 只放一條是 skill 明列的禁止事項。 07d7267 加的是單一 #FFFFFF,部落格沒有深色模式所以影響小,但 anti 主題切換時要用 JavaScript 同步更新,這也還沒做。
👥 適合誰、不適合誰?
適合:
- 網站有大量手機流量、但開發和測試都在桌機上做的人
- 做 PWA、底部導覽、bottom sheet、全螢幕版面的前端
- 想在上線前快速盤點「哪些看程式碼就能確定、哪些一定要實機」的人。它的確定度分類對排測試優先序很有用
- 寫原生 iOS/Android App。這支只處理瀏覽器,原生 Swift 看同 repo 的
write-swift,React Native 看animate-expo - 想調整動畫手感。那是
animate和 apple-design 的範圍 - 沒有任何手機可以測的情況。它的一半價值在告訴你「這項要上實機」,沒有實機就只拿到一半
📚 延伸資源
- skill 原始檔:github.com/emilkowalski/skills 的
skills/mobile-native/SKILL.md(MIT) - 同作者的網頁動效規範:apple-design skill 實測
- 同一個 commit 裡的目錄箭頭與篩選動畫,是 find-animation-opportunities 挑出來的
100vh 和 safe-area。兩個都有、而且 viewport-fit=cover 也在,再拿手機實際點一次輸入框和選單,這兩項模擬器騙不了你也幫不了你。