<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Fallback on Tarragon</title><link>https://tarrragon.github.io/blog/tags/fallback/</link><description>Recent content in Fallback on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 19 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/fallback/index.xml" rel="self" type="application/rss+xml"/><item><title>Gate 分類與三問設計法</title><link>https://tarrragon.github.io/blog/ux-design/02-gate-fallback/gate-three-questions/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/02-gate-fallback/gate-three-questions/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/ux-design/knowledge-cards/gate/" data-link-title="Gate（UX）" data-link-desc="說明使用者操作流程中「必須通過才能繼續」的關卡，以及成功/失敗/不確定三條路徑的設計責任">Gate&lt;/a> 是使用者操作流程中的「必須通過才能繼續」的關卡。生物辨識認證、網路連線檢查、權限請求、版本檢查 — 這些都是 gate。Gate 設計的核心責任是確保使用者在每種結果下都有路可走，而非只設計「通過」的情境。&lt;/p>
&lt;h2 id="三問設計法">三問設計法&lt;/h2>
&lt;p>每個 gate 設計時回答三個問題：&lt;/p>
&lt;h3 id="成功時做什麼">成功時做什麼&lt;/h3>
&lt;p>Gate 通過後使用者進入下一步。這是最直覺的設計 — 認證成功進入主畫面、網路連線成功開始載入資料、權限授予後啟用功能。&lt;/p>
&lt;p>成功路徑通常是設計時最先考慮的，也是最不容易遺漏的。&lt;/p>
&lt;h3 id="失敗時做什麼">失敗時做什麼&lt;/h3>
&lt;p>Gate 未通過時使用者的&lt;a href="https://tarrragon.github.io/blog/ux-design/knowledge-cards/ux-fallback/" data-link-title="Fallback（UX）" data-link-desc="說明 gate 未通過時使用者的替代路徑，和 backend fallback（server-side 降級）的語意區別">替代路徑&lt;/a>。替代路徑可以是：降級功能（部分功能可用）、替代驗證方式（密碼代替 Face ID）、手動重試（重試按鈕）、放棄操作（返回上一頁）。&lt;/p>
&lt;p>失敗路徑是最容易遺漏的。app_tunnel 的 biometric gate 設定 &lt;code>biometricOnly: true&lt;/code>，Face ID 不可用時使用者直接被擋住，沒有密碼 fallback、沒有跳過選項、沒有返回路徑（&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2&lt;/a>）。修復只改一個 boolean — &lt;code>biometricOnly: false&lt;/code> — 讓系統自動提示輸入裝置密碼。但這個決策應該在企劃階段做，而非實機測試時才發現。&lt;/p>
&lt;h3 id="使用者不知道發生什麼時做什麼">使用者不知道發生什麼時做什麼&lt;/h3>
&lt;p>Gate 處理中（loading）或結果不確定（timeout）時使用者看到什麼、能做什麼。&lt;/p>
&lt;p>使用者不知道發生什麼的情境包括：認證彈窗尚未出現（系統延遲）、網路請求已發但未回應（loading）、權限對話框被系統遮擋（多個 dialog 堆疊）。&lt;/p>
&lt;p>在這個狀態下使用者需要的是：知道系統在做什麼（loading 指示）、可以取消等待（取消按鈕）、超過合理時間後有提示（timeout 訊息 + 重試選項）。&lt;/p>
&lt;h2 id="gate-的四種常見類型">Gate 的四種常見類型&lt;/h2>
&lt;h3 id="認證-gate">認證 Gate&lt;/h3>
&lt;p>使用者必須驗證身份才能使用功能。生物辨識、密碼、PIN 碼、OAuth 登入。&lt;/p>
&lt;p>認證 gate 的 fallback 設計取決於安全需求和使用場景。銀行 app 可能要求生物辨識 + PIN 碼雙重驗證，沒有更低層級的 fallback。自用工具可以接受密碼 fallback，因為使用者本身就是 owner — 可用性優先於認證強度（&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2&lt;/a>）。&lt;/p>
&lt;h3 id="網路-gate">網路 Gate&lt;/h3>
&lt;p>功能需要網路連線才能運作。連線存在但不穩定的場景比完全離線更難處理 — 請求可能成功、可能逾時、可能部分成功。&lt;/p>
&lt;h3 id="權限-gate">權限 Gate&lt;/h3>
&lt;p>App 需要系統權限（相機、位置、通知）才能使用特定功能。&lt;/p>
&lt;p>權限 gate 的特殊性在於使用者可以永久拒絕。拒絕後再次請求不會彈出系統對話框 — 必須引導使用者到系統設定手動開啟。&lt;/p>
&lt;h3 id="環境-gate">環境 Gate&lt;/h3>
&lt;p>特定的硬體或軟體條件必須滿足。最低 OS 版本、特定感測器（NFC、深度相機）、特定連接（藍牙已開啟）。&lt;/p>
&lt;p>環境 gate 的 fallback 通常有限 — 硬體不存在時無法用軟體模擬。但至少應該告知使用者為什麼功能不可用，而非靜默禁用。&lt;/p>
&lt;h3 id="其他常見-gate">其他常見 Gate&lt;/h3>
&lt;p>商業 app 還有兩種 gate 在本系列涵蓋範圍之外但實務常見：&lt;/p>
&lt;p>&lt;strong>付費 Gate&lt;/strong>（paywall）：功能需要付費才能使用。付費 gate 的 fallback 設計和上述四種不同 — 「失敗」路徑的目標是引導使用者付費而非提供替代功能。試用期、降級功能、付費引導 vs 付費強制的取捨依賴商業模式決策。&lt;/p>
&lt;p>&lt;strong>版本相容性 Gate&lt;/strong>：API 版本過舊需要升級 app。Fallback 是提示使用者更新，但強制更新會阻擋無法更新的使用者（舊 OS 版本不支援新版 app）。&lt;/p>
&lt;h2 id="gate-設計表">Gate 設計表&lt;/h2>
&lt;p>把三問設計法應用到每個 gate，產出一張設計表：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Gate&lt;/th>
 &lt;th>成功&lt;/th>
 &lt;th>失敗&lt;/th>
 &lt;th>不確定&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>生物辨識&lt;/td>
 &lt;td>進入主畫面&lt;/td>
 &lt;td>提示輸入裝置密碼&lt;/td>
 &lt;td>顯示「驗證中」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>網路連線&lt;/td>
 &lt;td>開始載入資料&lt;/td>
 &lt;td>顯示離線提示 + 重試&lt;/td>
 &lt;td>顯示 loading + 取消&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>相機權限&lt;/td>
 &lt;td>開啟掃描功能&lt;/td>
 &lt;td>說明原因 + 設定連結&lt;/td>
 &lt;td>等待系統對話框&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>藍牙&lt;/td>
 &lt;td>開始裝置搜尋&lt;/td>
 &lt;td>提示開啟藍牙 + 連結&lt;/td>
 &lt;td>顯示搜尋中 + 取消&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>失敗欄和不確定欄為空的 gate 就是 UX 死胡同的候選 — 和&lt;a href="https://tarrragon.github.io/blog/ux-design/01-screen-state-machine/state-matrix-definition/" data-link-title="畫面狀態矩陣的定義與填寫方法" data-link-desc="四欄矩陣（顯示 / 可用操作 / 進入條件 / 退出路徑）的定義、填寫步驟和檢查規則 — 退出路徑為空 = UX 死胡同">畫面狀態矩陣&lt;/a>的退出路徑檢查同樣的邏輯。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/ux-design/knowledge-cards/gate/" data-link-title="Gate（UX）" data-link-desc="說明使用者操作流程中「必須通過才能繼續」的關卡，以及成功/失敗/不確定三條路徑的設計責任">Gate</a> 是使用者操作流程中的「必須通過才能繼續」的關卡。生物辨識認證、網路連線檢查、權限請求、版本檢查 — 這些都是 gate。Gate 設計的核心責任是確保使用者在每種結果下都有路可走，而非只設計「通過」的情境。</p>
<h2 id="三問設計法">三問設計法</h2>
<p>每個 gate 設計時回答三個問題：</p>
<h3 id="成功時做什麼">成功時做什麼</h3>
<p>Gate 通過後使用者進入下一步。這是最直覺的設計 — 認證成功進入主畫面、網路連線成功開始載入資料、權限授予後啟用功能。</p>
<p>成功路徑通常是設計時最先考慮的，也是最不容易遺漏的。</p>
<h3 id="失敗時做什麼">失敗時做什麼</h3>
<p>Gate 未通過時使用者的<a href="/blog/ux-design/knowledge-cards/ux-fallback/" data-link-title="Fallback（UX）" data-link-desc="說明 gate 未通過時使用者的替代路徑，和 backend fallback（server-side 降級）的語意區別">替代路徑</a>。替代路徑可以是：降級功能（部分功能可用）、替代驗證方式（密碼代替 Face ID）、手動重試（重試按鈕）、放棄操作（返回上一頁）。</p>
<p>失敗路徑是最容易遺漏的。app_tunnel 的 biometric gate 設定 <code>biometricOnly: true</code>，Face ID 不可用時使用者直接被擋住，沒有密碼 fallback、沒有跳過選項、沒有返回路徑（<a href="/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2</a>）。修復只改一個 boolean — <code>biometricOnly: false</code> — 讓系統自動提示輸入裝置密碼。但這個決策應該在企劃階段做，而非實機測試時才發現。</p>
<h3 id="使用者不知道發生什麼時做什麼">使用者不知道發生什麼時做什麼</h3>
<p>Gate 處理中（loading）或結果不確定（timeout）時使用者看到什麼、能做什麼。</p>
<p>使用者不知道發生什麼的情境包括：認證彈窗尚未出現（系統延遲）、網路請求已發但未回應（loading）、權限對話框被系統遮擋（多個 dialog 堆疊）。</p>
<p>在這個狀態下使用者需要的是：知道系統在做什麼（loading 指示）、可以取消等待（取消按鈕）、超過合理時間後有提示（timeout 訊息 + 重試選項）。</p>
<h2 id="gate-的四種常見類型">Gate 的四種常見類型</h2>
<h3 id="認證-gate">認證 Gate</h3>
<p>使用者必須驗證身份才能使用功能。生物辨識、密碼、PIN 碼、OAuth 登入。</p>
<p>認證 gate 的 fallback 設計取決於安全需求和使用場景。銀行 app 可能要求生物辨識 + PIN 碼雙重驗證，沒有更低層級的 fallback。自用工具可以接受密碼 fallback，因為使用者本身就是 owner — 可用性優先於認證強度（<a href="/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2</a>）。</p>
<h3 id="網路-gate">網路 Gate</h3>
<p>功能需要網路連線才能運作。連線存在但不穩定的場景比完全離線更難處理 — 請求可能成功、可能逾時、可能部分成功。</p>
<h3 id="權限-gate">權限 Gate</h3>
<p>App 需要系統權限（相機、位置、通知）才能使用特定功能。</p>
<p>權限 gate 的特殊性在於使用者可以永久拒絕。拒絕後再次請求不會彈出系統對話框 — 必須引導使用者到系統設定手動開啟。</p>
<h3 id="環境-gate">環境 Gate</h3>
<p>特定的硬體或軟體條件必須滿足。最低 OS 版本、特定感測器（NFC、深度相機）、特定連接（藍牙已開啟）。</p>
<p>環境 gate 的 fallback 通常有限 — 硬體不存在時無法用軟體模擬。但至少應該告知使用者為什麼功能不可用，而非靜默禁用。</p>
<h3 id="其他常見-gate">其他常見 Gate</h3>
<p>商業 app 還有兩種 gate 在本系列涵蓋範圍之外但實務常見：</p>
<p><strong>付費 Gate</strong>（paywall）：功能需要付費才能使用。付費 gate 的 fallback 設計和上述四種不同 — 「失敗」路徑的目標是引導使用者付費而非提供替代功能。試用期、降級功能、付費引導 vs 付費強制的取捨依賴商業模式決策。</p>
<p><strong>版本相容性 Gate</strong>：API 版本過舊需要升級 app。Fallback 是提示使用者更新，但強制更新會阻擋無法更新的使用者（舊 OS 版本不支援新版 app）。</p>
<h2 id="gate-設計表">Gate 設計表</h2>
<p>把三問設計法應用到每個 gate，產出一張設計表：</p>
<table>
  <thead>
      <tr>
          <th>Gate</th>
          <th>成功</th>
          <th>失敗</th>
          <th>不確定</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>生物辨識</td>
          <td>進入主畫面</td>
          <td>提示輸入裝置密碼</td>
          <td>顯示「驗證中」</td>
      </tr>
      <tr>
          <td>網路連線</td>
          <td>開始載入資料</td>
          <td>顯示離線提示 + 重試</td>
          <td>顯示 loading + 取消</td>
      </tr>
      <tr>
          <td>相機權限</td>
          <td>開啟掃描功能</td>
          <td>說明原因 + 設定連結</td>
          <td>等待系統對話框</td>
      </tr>
      <tr>
          <td>藍牙</td>
          <td>開始裝置搜尋</td>
          <td>提示開啟藍牙 + 連結</td>
          <td>顯示搜尋中 + 取消</td>
      </tr>
  </tbody>
</table>
<p>失敗欄和不確定欄為空的 gate 就是 UX 死胡同的候選 — 和<a href="/blog/ux-design/01-screen-state-machine/state-matrix-definition/" data-link-title="畫面狀態矩陣的定義與填寫方法" data-link-desc="四欄矩陣（顯示 / 可用操作 / 進入條件 / 退出路徑）的定義、填寫步驟和檢查規則 — 退出路徑為空 = UX 死胡同">畫面狀態矩陣</a>的退出路徑檢查同樣的邏輯。</p>
<p>三問設計法的具體應用在 <a href="/blog/ux-design/02-gate-fallback/biometric-fallback-design/" data-link-title="Biometric fallback 完整設計" data-link-desc="iOS Face ID / Touch ID 和 Android BiometricPrompt 的行為差異、fallback 策略、安全 vs 可用性取捨的顯式記錄方法">Biometric fallback 完整設計</a>中以生物辨識 gate 為例展開。Gate 在開發環境的行為可能和真機不同，<a href="/blog/ux-design/02-gate-fallback/dev-vs-real-gate-behavior/" data-link-title="開發環境 vs 真機的 gate 行為差異表" data-link-desc="模擬器、debug build、test 環境中的 gate 行為和真機 release build 不同 — 差異表讓開發者在上機前知道哪些 gate 還沒被真實驗證">開發環境 vs 真機的 gate 行為差異表</a>列出每個 gate 在模擬器和真機上的差異。Gate 設計表的「失敗」欄和<a href="/blog/ux-design/01-screen-state-machine/" data-link-title="模組一：畫面狀態機設計" data-link-desc="畫面狀態矩陣（顯示 / 操作 / 進入 / 退出）— 退出路徑為空 = UX 死胡同">畫面狀態矩陣</a>的「退出路徑」欄是同一個問題在不同層級的表達。</p>
]]></content:encoded></item><item><title>Biometric fallback 完整設計</title><link>https://tarrragon.github.io/blog/ux-design/02-gate-fallback/biometric-fallback-design/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/02-gate-fallback/biometric-fallback-design/</guid><description>&lt;p>Biometric &lt;a href="https://tarrragon.github.io/blog/ux-design/knowledge-cards/gate/" data-link-title="Gate（UX）" data-link-desc="說明使用者操作流程中「必須通過才能繼續」的關卡，以及成功/失敗/不確定三條路徑的設計責任">gate&lt;/a> 的 &lt;a href="https://tarrragon.github.io/blog/ux-design/knowledge-cards/ux-fallback/" data-link-title="Fallback（UX）" data-link-desc="說明 gate 未通過時使用者的替代路徑，和 backend fallback（server-side 降級）的語意區別">fallback&lt;/a> 設計需要理解兩件事：平台的認證 API 在不同情境下的行為差異，以及安全收益和可用性代價之間的顯式取捨。&lt;/p>
&lt;h2 id="生物辨識失敗的情境">生物辨識失敗的情境&lt;/h2>
&lt;p>生物辨識失敗有多種原因，每種原因對使用者的影響和合理的 fallback 不同。&lt;/p>
&lt;h3 id="暫時性失敗">暫時性失敗&lt;/h3>
&lt;p>Face ID 因光線不足辨識失敗、指紋因手指潮濕讀取失敗。使用者的生物特徵正常，只是當次辨識條件不佳。重試可能成功。&lt;/p>
&lt;h3 id="持續性失敗">持續性失敗&lt;/h3>
&lt;p>使用者戴口罩讓 Face ID 無法辨識（較舊的 iOS 版本）、手指受傷影響指紋辨識。生物特徵暫時改變，短期內重試都不會成功。需要替代認證方式。&lt;/p>
&lt;h3 id="硬體不可用">硬體不可用&lt;/h3>
&lt;p>裝置沒有 Face ID / Touch ID 模組（較舊機型）、模擬器不支援生物辨識、生物辨識功能被裝置管理策略（MDM）禁用。需要替代認證方式。&lt;/p>
&lt;h3 id="使用者未設定">使用者未設定&lt;/h3>
&lt;p>裝置有硬體但使用者沒有設定 Face ID 或指紋。系統的 &lt;code>canCheckBiometrics&lt;/code> 回傳 &lt;code>true&lt;/code>（硬體存在）但實際認證會失敗。需要引導使用者設定或提供替代認證。&lt;/p>
&lt;h2 id="ios-和-android-的行為差異">iOS 和 Android 的行為差異&lt;/h2>
&lt;h3 id="ioslocalauthentication">iOS（LocalAuthentication）&lt;/h3>
&lt;p>iOS 的 &lt;code>LAContext.evaluatePolicy&lt;/code> 有兩個 policy：&lt;/p>
&lt;ul>
&lt;li>&lt;code>deviceOwnerAuthenticationWithBiometrics&lt;/code>：只接受生物辨識，失敗後不自動提示密碼&lt;/li>
&lt;li>&lt;code>deviceOwnerAuthentication&lt;/code>：先嘗試生物辨識，失敗後系統自動彈出裝置密碼輸入&lt;/li>
&lt;/ul>
&lt;p>Flutter 的 &lt;code>local_auth&lt;/code> 套件的 &lt;code>biometricOnly&lt;/code> 參數對應這兩個 policy。&lt;code>biometricOnly: true&lt;/code> 用前者，&lt;code>biometricOnly: false&lt;/code> 用後者。&lt;/p>
&lt;p>iOS 的行為特點：系統控制認證 UI（不是 app 自行繪製），認證失敗次數過多會自動鎖定（需要輸入密碼解鎖），Face ID 多次失敗後系統會自動提供密碼選項（即使 app 要求 biometricOnly）。&lt;/p>
&lt;h3 id="androidbiometricprompt">Android（BiometricPrompt）&lt;/h3>
&lt;p>Android 的 BiometricPrompt 分成三個 class：&lt;/p>
&lt;ul>
&lt;li>&lt;code>BIOMETRIC_STRONG&lt;/code>：只接受 Class 3 生物辨識（經過硬體安全模組驗證的指紋/面部）&lt;/li>
&lt;li>&lt;code>BIOMETRIC_WEAK&lt;/code>：接受 Class 2 和 Class 3 生物辨識&lt;/li>
&lt;li>&lt;code>DEVICE_CREDENTIAL&lt;/code>：接受裝置 PIN/圖形/密碼&lt;/li>
&lt;/ul>
&lt;p>三個 class 可以用 &lt;code>|&lt;/code> 組合。&lt;code>BIOMETRIC_STRONG | DEVICE_CREDENTIAL&lt;/code> 表示先嘗試強生物辨識，失敗後 fallback 到裝置密碼。&lt;/p>
&lt;p>Android 的行為特點：不同廠商的生物辨識品質差異大（Samsung 的面部辨識和 Pixel 的面部辨識安全等級不同）、部分裝置的指紋感測器在螢幕下方（使用者可能不知道在哪裡觸碰）。&lt;/p>
&lt;h2 id="安全-vs-可用性的顯式取捨">安全 vs 可用性的顯式取捨&lt;/h2>
&lt;p>&lt;code>biometricOnly&lt;/code> 的決策涉及安全和可用性的取捨。這個取捨應該在功能規格中顯式記錄，讓後續的 code review 和維護者能理解決策的背景。&lt;/p>
&lt;p>記錄格式建議：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">Gate: biometric authentication
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">Decision: biometricOnly = false (allow device credential fallback)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">Security trade-off: device credential (PIN/password) is weaker than biometric
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">Rationale: self-hosted tool, user = owner, availability &amp;gt; auth strength
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">Risk accepted: someone with device PIN can access the app&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>app_tunnel 選擇 &lt;code>biometricOnly: true&lt;/code> 的原始意圖是「安全性更高」，但沒有顯式記錄取捨，也沒有評估「Face ID 不可用時使用者完全無法使用 app」的代價。自用工具的使用者就是 owner，密碼 fallback 的安全風險遠低於完全無法使用的可用性風險（&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2&lt;/a>）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>Gate 設計的通用方法論 → &lt;a href="https://tarrragon.github.io/blog/ux-design/02-gate-fallback/gate-three-questions/" data-link-title="Gate 分類與三問設計法" data-link-desc="每個 gate 設計時問三個問題：成功時做什麼、失敗時做什麼、使用者不知道發生什麼時做什麼">Gate 分類與三問設計法&lt;/a>&lt;/li>
&lt;li>開發環境遮蔽 gate 問題 → &lt;a href="https://tarrragon.github.io/blog/ux-design/02-gate-fallback/dev-vs-real-gate-behavior/" data-link-title="開發環境 vs 真機的 gate 行為差異表" data-link-desc="模擬器、debug build、test 環境中的 gate 行為和真機 release build 不同 — 差異表讓開發者在上機前知道哪些 gate 還沒被真實驗證">開發環境 vs 真機的 gate 行為差異表&lt;/a>&lt;/li>
&lt;li>安全 vs 可用性在 monitoring 中的對應 → &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">monitoring 模組七 資安&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Biometric <a href="/blog/ux-design/knowledge-cards/gate/" data-link-title="Gate（UX）" data-link-desc="說明使用者操作流程中「必須通過才能繼續」的關卡，以及成功/失敗/不確定三條路徑的設計責任">gate</a> 的 <a href="/blog/ux-design/knowledge-cards/ux-fallback/" data-link-title="Fallback（UX）" data-link-desc="說明 gate 未通過時使用者的替代路徑，和 backend fallback（server-side 降級）的語意區別">fallback</a> 設計需要理解兩件事：平台的認證 API 在不同情境下的行為差異，以及安全收益和可用性代價之間的顯式取捨。</p>
<h2 id="生物辨識失敗的情境">生物辨識失敗的情境</h2>
<p>生物辨識失敗有多種原因，每種原因對使用者的影響和合理的 fallback 不同。</p>
<h3 id="暫時性失敗">暫時性失敗</h3>
<p>Face ID 因光線不足辨識失敗、指紋因手指潮濕讀取失敗。使用者的生物特徵正常，只是當次辨識條件不佳。重試可能成功。</p>
<h3 id="持續性失敗">持續性失敗</h3>
<p>使用者戴口罩讓 Face ID 無法辨識（較舊的 iOS 版本）、手指受傷影響指紋辨識。生物特徵暫時改變，短期內重試都不會成功。需要替代認證方式。</p>
<h3 id="硬體不可用">硬體不可用</h3>
<p>裝置沒有 Face ID / Touch ID 模組（較舊機型）、模擬器不支援生物辨識、生物辨識功能被裝置管理策略（MDM）禁用。需要替代認證方式。</p>
<h3 id="使用者未設定">使用者未設定</h3>
<p>裝置有硬體但使用者沒有設定 Face ID 或指紋。系統的 <code>canCheckBiometrics</code> 回傳 <code>true</code>（硬體存在）但實際認證會失敗。需要引導使用者設定或提供替代認證。</p>
<h2 id="ios-和-android-的行為差異">iOS 和 Android 的行為差異</h2>
<h3 id="ioslocalauthentication">iOS（LocalAuthentication）</h3>
<p>iOS 的 <code>LAContext.evaluatePolicy</code> 有兩個 policy：</p>
<ul>
<li><code>deviceOwnerAuthenticationWithBiometrics</code>：只接受生物辨識，失敗後不自動提示密碼</li>
<li><code>deviceOwnerAuthentication</code>：先嘗試生物辨識，失敗後系統自動彈出裝置密碼輸入</li>
</ul>
<p>Flutter 的 <code>local_auth</code> 套件的 <code>biometricOnly</code> 參數對應這兩個 policy。<code>biometricOnly: true</code> 用前者，<code>biometricOnly: false</code> 用後者。</p>
<p>iOS 的行為特點：系統控制認證 UI（不是 app 自行繪製），認證失敗次數過多會自動鎖定（需要輸入密碼解鎖），Face ID 多次失敗後系統會自動提供密碼選項（即使 app 要求 biometricOnly）。</p>
<h3 id="androidbiometricprompt">Android（BiometricPrompt）</h3>
<p>Android 的 BiometricPrompt 分成三個 class：</p>
<ul>
<li><code>BIOMETRIC_STRONG</code>：只接受 Class 3 生物辨識（經過硬體安全模組驗證的指紋/面部）</li>
<li><code>BIOMETRIC_WEAK</code>：接受 Class 2 和 Class 3 生物辨識</li>
<li><code>DEVICE_CREDENTIAL</code>：接受裝置 PIN/圖形/密碼</li>
</ul>
<p>三個 class 可以用 <code>|</code> 組合。<code>BIOMETRIC_STRONG | DEVICE_CREDENTIAL</code> 表示先嘗試強生物辨識，失敗後 fallback 到裝置密碼。</p>
<p>Android 的行為特點：不同廠商的生物辨識品質差異大（Samsung 的面部辨識和 Pixel 的面部辨識安全等級不同）、部分裝置的指紋感測器在螢幕下方（使用者可能不知道在哪裡觸碰）。</p>
<h2 id="安全-vs-可用性的顯式取捨">安全 vs 可用性的顯式取捨</h2>
<p><code>biometricOnly</code> 的決策涉及安全和可用性的取捨。這個取捨應該在功能規格中顯式記錄，讓後續的 code review 和維護者能理解決策的背景。</p>
<p>記錄格式建議：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Gate: biometric authentication
</span></span><span class="line"><span class="ln">2</span><span class="cl">Decision: biometricOnly = false (allow device credential fallback)
</span></span><span class="line"><span class="ln">3</span><span class="cl">Security trade-off: device credential (PIN/password) is weaker than biometric
</span></span><span class="line"><span class="ln">4</span><span class="cl">Rationale: self-hosted tool, user = owner, availability &gt; auth strength
</span></span><span class="line"><span class="ln">5</span><span class="cl">Risk accepted: someone with device PIN can access the app</span></span></code></pre></div><p>app_tunnel 選擇 <code>biometricOnly: true</code> 的原始意圖是「安全性更高」，但沒有顯式記錄取捨，也沒有評估「Face ID 不可用時使用者完全無法使用 app」的代價。自用工具的使用者就是 owner，密碼 fallback 的安全風險遠低於完全無法使用的可用性風險（<a href="/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2</a>）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Gate 設計的通用方法論 → <a href="/blog/ux-design/02-gate-fallback/gate-three-questions/" data-link-title="Gate 分類與三問設計法" data-link-desc="每個 gate 設計時問三個問題：成功時做什麼、失敗時做什麼、使用者不知道發生什麼時做什麼">Gate 分類與三問設計法</a></li>
<li>開發環境遮蔽 gate 問題 → <a href="/blog/ux-design/02-gate-fallback/dev-vs-real-gate-behavior/" data-link-title="開發環境 vs 真機的 gate 行為差異表" data-link-desc="模擬器、debug build、test 環境中的 gate 行為和真機 release build 不同 — 差異表讓開發者在上機前知道哪些 gate 還沒被真實驗證">開發環境 vs 真機的 gate 行為差異表</a></li>
<li>安全 vs 可用性在 monitoring 中的對應 → <a href="/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">monitoring 模組七 資安</a></li>
</ul>
]]></content:encoded></item><item><title>模組二：Gate 與 Fallback 設計</title><link>https://tarrragon.github.io/blog/ux-design/02-gate-fallback/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/02-gate-fallback/</guid><description>&lt;p>回答「使用者過不了關卡時怎麼辦」。&lt;/p>
&lt;h2 id="對應-findings">對應 findings&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Finding&lt;/th>
 &lt;th>來源&lt;/th>
 &lt;th>內容&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>UF-4&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2&lt;/a>&lt;/td>
 &lt;td>biometricOnly 安全收益 vs 可用性代價 — &lt;strong>本模組主寫&lt;/strong>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>UF-5&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2&lt;/a>&lt;/td>
 &lt;td>開發環境遮蔽 gate 問題（模擬器行為 vs 真機）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="待寫章節">待寫章節&lt;/h2>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Gate 分類與三問設計法（成功 / 失敗 / 使用者不知道發生什麼）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Biometric fallback 完整設計（iOS/Android 差異）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 網路斷線 UX 模式（offline-first / retry / degraded mode）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Permission 請求時機與措辭&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 開發環境 vs 真機的 gate 行為差異表&lt;/li>
&lt;/ul>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/testing/01-test-strategy-layers/" data-link-title="模組一：測試策略分層" data-link-desc="Unit / Protocol Integration / Screen State 三層測試各自的職責、盲區和判斷原則">testing 模組一 測試策略&lt;/a>：gate fallback 的 mock vs 真機行為差異需要 protocol test&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">monitoring 模組七 資安&lt;/a>：biometric fallback 的安全 vs 可用性取捨&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>回答「使用者過不了關卡時怎麼辦」。</p>
<h2 id="對應-findings">對應 findings</h2>
<table>
  <thead>
      <tr>
          <th>Finding</th>
          <th>來源</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>UF-4</td>
          <td><a href="/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2</a></td>
          <td>biometricOnly 安全收益 vs 可用性代價 — <strong>本模組主寫</strong></td>
      </tr>
      <tr>
          <td>UF-5</td>
          <td><a href="/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2</a></td>
          <td>開發環境遮蔽 gate 問題（模擬器行為 vs 真機）</td>
      </tr>
  </tbody>
</table>
<h2 id="待寫章節">待寫章節</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> Gate 分類與三問設計法（成功 / 失敗 / 使用者不知道發生什麼）</li>
<li><input checked="" disabled="" type="checkbox"> Biometric fallback 完整設計（iOS/Android 差異）</li>
<li><input checked="" disabled="" type="checkbox"> 網路斷線 UX 模式（offline-first / retry / degraded mode）</li>
<li><input checked="" disabled="" type="checkbox"> Permission 請求時機與措辭</li>
<li><input checked="" disabled="" type="checkbox"> 開發環境 vs 真機的 gate 行為差異表</li>
</ul>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/testing/01-test-strategy-layers/" data-link-title="模組一：測試策略分層" data-link-desc="Unit / Protocol Integration / Screen State 三層測試各自的職責、盲區和判斷原則">testing 模組一 測試策略</a>：gate fallback 的 mock vs 真機行為差異需要 protocol test</li>
<li>→ <a href="/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">monitoring 模組七 資安</a>：biometric fallback 的安全 vs 可用性取捨</li>
</ul>
]]></content:encoded></item><item><title>Degraded mode 設計</title><link>https://tarrragon.github.io/blog/ux-design/04-error-recovery/degraded-mode-design/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/04-error-recovery/degraded-mode-design/</guid><description>&lt;p>Degraded mode 是指系統的部分功能因為外部依賴不可用（網路斷線、服務故障、權限缺失）而暫時無法運作，但其他功能仍可正常使用。設計重點是讓使用者清楚知道哪些功能可用、哪些暫時不可用，而非讓整個 app 因為一個功能的失敗而停擺。&lt;/p>
&lt;h2 id="三種處理方式">三種處理方式&lt;/h2>
&lt;h3 id="靜默隱藏">靜默隱藏&lt;/h3>
&lt;p>把不可用的功能從 UI 上移除 — 按鈕消失、選單項目隱藏。使用者看不到這些功能，自然不會嘗試使用。&lt;/p>
&lt;p>靜默隱藏的風險是使用者困惑。經常使用的功能突然消失，使用者會以為是 bug 或 app 更新移除了功能。如果功能在恢復後重新出現，使用者的困惑加劇。&lt;/p>
&lt;p>靜默隱藏只適合使用者從未使用過的功能（新使用者尚未配對時隱藏連線按鈕）。已經使用過的功能突然隱藏會破壞使用者的心理模型。&lt;/p>
&lt;h3 id="明確標示">明確標示&lt;/h3>
&lt;p>功能的 UI 元素保留在畫面上，但加上不可用的視覺標示 — 灰色按鈕、「離線不可用」標籤、禁用狀態。使用者能看到功能存在，也知道目前暫時無法使用。&lt;/p>
&lt;p>明確標示的設計要點：&lt;/p>
&lt;p>&lt;strong>說明原因&lt;/strong>。「搜尋功能需要網路連線」比單純的灰色按鈕提供更多資訊。使用者知道原因後能自行判斷要等還是離開。&lt;/p>
&lt;p>&lt;strong>說明恢復條件&lt;/strong>。「連上網路後自動恢復」讓使用者知道什麼時候功能會回來。「重新啟動 app 後可用」讓使用者知道需要採取行動。&lt;/p>
&lt;p>&lt;strong>避免只靠顏色傳達狀態&lt;/strong>。灰色按鈕對色盲使用者可能不明顯。搭配文字標籤或圖示。&lt;/p>
&lt;h3 id="替代方案">替代方案&lt;/h3>
&lt;p>提供不需要失敗依賴的替代功能。線上搜尋不可用時提供離線搜尋（本地快取的資料）。即時同步不可用時提供本地儲存（恢復連線後自動同步）。&lt;/p>
&lt;p>替代方案的 UX 需要讓使用者知道目前使用的是替代版本。「離線模式 — 搜尋結果可能不是最新的」讓使用者對結果的準確度有正確預期。&lt;/p>
&lt;h2 id="全域-vs-功能級降級">全域 vs 功能級降級&lt;/h2>
&lt;h3 id="全域降級">全域降級&lt;/h3>
&lt;p>整個 app 進入降級模式 — 頂部顯示「離線模式」橫幅，所有需要網路的功能統一標示為不可用。適合網路連線是 app 核心依賴的場景。&lt;/p>
&lt;p>全域降級的 UI 實作簡單（一個全域狀態控制所有功能的可用性），但可能過度限制 — 部分功能不依賴網路也能運作。&lt;/p>
&lt;h3 id="功能級降級">功能級降級&lt;/h3>
&lt;p>每個功能獨立判斷自己的可用狀態。搜尋需要網路但筆記不需要 — 網路斷線時搜尋不可用，筆記正常。&lt;/p>
&lt;p>功能級降級更精確但實作更複雜 — 每個功能需要宣告自己的依賴，並在依賴不可用時提供對應的 UI 狀態。&lt;/p>
&lt;h2 id="降級狀態的進入和退出">降級狀態的進入和退出&lt;/h2>
&lt;h3 id="進入">進入&lt;/h3>
&lt;p>依賴不可用時自動進入降級狀態。進入時通知使用者（Snackbar、橫幅、狀態變更）。&lt;/p>
&lt;p>避免頻繁切換 — 網路訊號不穩定時，每秒在正常和降級之間切換會讓 UI 閃爍。加入穩定性判斷（連續 N 秒不可用才進入降級，連續 N 秒可用才退出降級）。&lt;/p>
&lt;h3 id="退出">退出&lt;/h3>
&lt;p>依賴恢復後自動退出降級狀態。退出時通知使用者（「已恢復連線」），並自動執行待完成的操作（同步、補發事件）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>錯誤訊息的撰寫 → &lt;a href="https://tarrragon.github.io/blog/ux-design/04-error-recovery/error-message-principles/" data-link-title="錯誤訊息撰寫原則" data-link-desc="錯誤訊息的兩個職責：使用者能讀懂發生什麼、使用者能決定下一步做什麼">錯誤訊息撰寫原則&lt;/a>&lt;/li>
&lt;li>重試循環的逃生口 → &lt;a href="https://tarrragon.github.io/blog/ux-design/04-error-recovery/error-loop-escape/" data-link-title="error → retry → error 循環的逃生口設計" data-link-desc="當重試持續失敗時，使用者需要第二條路 — 逃生口設計讓使用者能離開失敗循環而非被困住">error → retry → error 循環的逃生口&lt;/a>&lt;/li>
&lt;li>網路 gate 的 UX 處理 → &lt;a href="https://tarrragon.github.io/blog/ux-design/02-gate-fallback/network-offline-ux/" data-link-title="網路斷線 UX 模式" data-link-desc="Offline-first / retry / degraded mode 三種網路 gate 的處理策略 — 取決於功能是否依賴即時連線">ux-design 模組二 網路斷線 UX 模式&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Degraded mode 是指系統的部分功能因為外部依賴不可用（網路斷線、服務故障、權限缺失）而暫時無法運作，但其他功能仍可正常使用。設計重點是讓使用者清楚知道哪些功能可用、哪些暫時不可用，而非讓整個 app 因為一個功能的失敗而停擺。</p>
<h2 id="三種處理方式">三種處理方式</h2>
<h3 id="靜默隱藏">靜默隱藏</h3>
<p>把不可用的功能從 UI 上移除 — 按鈕消失、選單項目隱藏。使用者看不到這些功能，自然不會嘗試使用。</p>
<p>靜默隱藏的風險是使用者困惑。經常使用的功能突然消失，使用者會以為是 bug 或 app 更新移除了功能。如果功能在恢復後重新出現，使用者的困惑加劇。</p>
<p>靜默隱藏只適合使用者從未使用過的功能（新使用者尚未配對時隱藏連線按鈕）。已經使用過的功能突然隱藏會破壞使用者的心理模型。</p>
<h3 id="明確標示">明確標示</h3>
<p>功能的 UI 元素保留在畫面上，但加上不可用的視覺標示 — 灰色按鈕、「離線不可用」標籤、禁用狀態。使用者能看到功能存在，也知道目前暫時無法使用。</p>
<p>明確標示的設計要點：</p>
<p><strong>說明原因</strong>。「搜尋功能需要網路連線」比單純的灰色按鈕提供更多資訊。使用者知道原因後能自行判斷要等還是離開。</p>
<p><strong>說明恢復條件</strong>。「連上網路後自動恢復」讓使用者知道什麼時候功能會回來。「重新啟動 app 後可用」讓使用者知道需要採取行動。</p>
<p><strong>避免只靠顏色傳達狀態</strong>。灰色按鈕對色盲使用者可能不明顯。搭配文字標籤或圖示。</p>
<h3 id="替代方案">替代方案</h3>
<p>提供不需要失敗依賴的替代功能。線上搜尋不可用時提供離線搜尋（本地快取的資料）。即時同步不可用時提供本地儲存（恢復連線後自動同步）。</p>
<p>替代方案的 UX 需要讓使用者知道目前使用的是替代版本。「離線模式 — 搜尋結果可能不是最新的」讓使用者對結果的準確度有正確預期。</p>
<h2 id="全域-vs-功能級降級">全域 vs 功能級降級</h2>
<h3 id="全域降級">全域降級</h3>
<p>整個 app 進入降級模式 — 頂部顯示「離線模式」橫幅，所有需要網路的功能統一標示為不可用。適合網路連線是 app 核心依賴的場景。</p>
<p>全域降級的 UI 實作簡單（一個全域狀態控制所有功能的可用性），但可能過度限制 — 部分功能不依賴網路也能運作。</p>
<h3 id="功能級降級">功能級降級</h3>
<p>每個功能獨立判斷自己的可用狀態。搜尋需要網路但筆記不需要 — 網路斷線時搜尋不可用，筆記正常。</p>
<p>功能級降級更精確但實作更複雜 — 每個功能需要宣告自己的依賴，並在依賴不可用時提供對應的 UI 狀態。</p>
<h2 id="降級狀態的進入和退出">降級狀態的進入和退出</h2>
<h3 id="進入">進入</h3>
<p>依賴不可用時自動進入降級狀態。進入時通知使用者（Snackbar、橫幅、狀態變更）。</p>
<p>避免頻繁切換 — 網路訊號不穩定時，每秒在正常和降級之間切換會讓 UI 閃爍。加入穩定性判斷（連續 N 秒不可用才進入降級，連續 N 秒可用才退出降級）。</p>
<h3 id="退出">退出</h3>
<p>依賴恢復後自動退出降級狀態。退出時通知使用者（「已恢復連線」），並自動執行待完成的操作（同步、補發事件）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>錯誤訊息的撰寫 → <a href="/blog/ux-design/04-error-recovery/error-message-principles/" data-link-title="錯誤訊息撰寫原則" data-link-desc="錯誤訊息的兩個職責：使用者能讀懂發生什麼、使用者能決定下一步做什麼">錯誤訊息撰寫原則</a></li>
<li>重試循環的逃生口 → <a href="/blog/ux-design/04-error-recovery/error-loop-escape/" data-link-title="error → retry → error 循環的逃生口設計" data-link-desc="當重試持續失敗時，使用者需要第二條路 — 逃生口設計讓使用者能離開失敗循環而非被困住">error → retry → error 循環的逃生口</a></li>
<li>網路 gate 的 UX 處理 → <a href="/blog/ux-design/02-gate-fallback/network-offline-ux/" data-link-title="網路斷線 UX 模式" data-link-desc="Offline-first / retry / degraded mode 三種網路 gate 的處理策略 — 取決於功能是否依賴即時連線">ux-design 模組二 網路斷線 UX 模式</a></li>
</ul>
]]></content:encoded></item><item><title>Fallback（UX）</title><link>https://tarrragon.github.io/blog/ux-design/knowledge-cards/ux-fallback/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/knowledge-cards/ux-fallback/</guid><description>&lt;p>UX fallback 的核心概念是「gate 未通過時使用者的替代路徑」。替代路徑可以是替代驗證方式（密碼代替 Face ID）、降級功能（部分功能可用）、手動重試、或放棄操作返回上一頁。和 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/fallback/" data-link-title="Fallback" data-link-desc="說明主要路徑失敗時使用替代結果或替代流程的設計責任">Fallback（Backend）&lt;/a> 不同，UX fallback 關注的是使用者體驗層的路徑設計，而非 server-side 的服務降級策略。可先對照 &lt;a href="https://tarrragon.github.io/blog/ux-design/knowledge-cards/gate/" data-link-title="Gate（UX）" data-link-desc="說明使用者操作流程中「必須通過才能繼續」的關卡，以及成功/失敗/不確定三條路徑的設計責任">Gate&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>UX fallback 位在 &lt;a href="https://tarrragon.github.io/blog/ux-design/knowledge-cards/gate/" data-link-title="Gate（UX）" data-link-desc="說明使用者操作流程中「必須通過才能繼續」的關卡，以及成功/失敗/不確定三條路徑的設計責任">Gate&lt;/a> 設計的失敗路徑中。Gate 的三問（成功/失敗/不確定）中，失敗路徑的具體內容就是 UX fallback。Backend 的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/fallback/" data-link-title="Fallback" data-link-desc="說明主要路徑失敗時使用替代結果或替代流程的設計責任">fallback&lt;/a> 是系統在依賴失敗時用替代結果維持服務，UX fallback 是使用者在 gate 失敗時的操作替代方案。兩者可能並存 — server-side fallback 提供降級資料，UX fallback 決定如何呈現這些降級資料給使用者。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>需要 UX fallback 的訊號是 gate 失敗時使用者完全無法繼續。常見情境：biometric 設定 &lt;code>biometricOnly: true&lt;/code> 導致 Face ID 失敗時沒有密碼 fallback、error 畫面只有重試按鈕沒有返回按鈕、網路斷線後所有功能不可用但部分功能不依賴網路。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>UX fallback 的設計責任是確保 gate 失敗時使用者有路可走。Fallback 的選擇取決於安全需求和使用場景 — 銀行 app 可能不提供低安全等級的 fallback，自用工具可以接受密碼 fallback 因為使用者就是 owner。安全 vs 可用性的取捨應在功能規格中顯式記錄。UX fallback 的存在應反映在&lt;a href="https://tarrragon.github.io/blog/ux-design/knowledge-cards/screen-state-matrix/" data-link-title="畫面狀態矩陣" data-link-desc="說明用四欄表格（顯示/可用操作/進入條件/退出路徑）系統性地暴露畫面導航缺口的設計工具">畫面狀態矩陣&lt;/a>的退出路徑欄中。&lt;/p></description><content:encoded><![CDATA[<p>UX fallback 的核心概念是「gate 未通過時使用者的替代路徑」。替代路徑可以是替代驗證方式（密碼代替 Face ID）、降級功能（部分功能可用）、手動重試、或放棄操作返回上一頁。和 <a href="/blog/backend/knowledge-cards/fallback/" data-link-title="Fallback" data-link-desc="說明主要路徑失敗時使用替代結果或替代流程的設計責任">Fallback（Backend）</a> 不同，UX fallback 關注的是使用者體驗層的路徑設計，而非 server-side 的服務降級策略。可先對照 <a href="/blog/ux-design/knowledge-cards/gate/" data-link-title="Gate（UX）" data-link-desc="說明使用者操作流程中「必須通過才能繼續」的關卡，以及成功/失敗/不確定三條路徑的設計責任">Gate</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>UX fallback 位在 <a href="/blog/ux-design/knowledge-cards/gate/" data-link-title="Gate（UX）" data-link-desc="說明使用者操作流程中「必須通過才能繼續」的關卡，以及成功/失敗/不確定三條路徑的設計責任">Gate</a> 設計的失敗路徑中。Gate 的三問（成功/失敗/不確定）中，失敗路徑的具體內容就是 UX fallback。Backend 的 <a href="/blog/backend/knowledge-cards/fallback/" data-link-title="Fallback" data-link-desc="說明主要路徑失敗時使用替代結果或替代流程的設計責任">fallback</a> 是系統在依賴失敗時用替代結果維持服務，UX fallback 是使用者在 gate 失敗時的操作替代方案。兩者可能並存 — server-side fallback 提供降級資料，UX fallback 決定如何呈現這些降級資料給使用者。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>需要 UX fallback 的訊號是 gate 失敗時使用者完全無法繼續。常見情境：biometric 設定 <code>biometricOnly: true</code> 導致 Face ID 失敗時沒有密碼 fallback、error 畫面只有重試按鈕沒有返回按鈕、網路斷線後所有功能不可用但部分功能不依賴網路。</p>
<h2 id="設計責任">設計責任</h2>
<p>UX fallback 的設計責任是確保 gate 失敗時使用者有路可走。Fallback 的選擇取決於安全需求和使用場景 — 銀行 app 可能不提供低安全等級的 fallback，自用工具可以接受密碼 fallback 因為使用者就是 owner。安全 vs 可用性的取捨應在功能規格中顯式記錄。UX fallback 的存在應反映在<a href="/blog/ux-design/knowledge-cards/screen-state-matrix/" data-link-title="畫面狀態矩陣" data-link-desc="說明用四欄表格（顯示/可用操作/進入條件/退出路徑）系統性地暴露畫面導航缺口的設計工具">畫面狀態矩陣</a>的退出路徑欄中。</p>
]]></content:encoded></item><item><title>網路斷線 UX 模式</title><link>https://tarrragon.github.io/blog/ux-design/02-gate-fallback/network-offline-ux/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/02-gate-fallback/network-offline-ux/</guid><description>&lt;p>網路 &lt;a href="https://tarrragon.github.io/blog/ux-design/knowledge-cards/gate/" data-link-title="Gate（UX）" data-link-desc="說明使用者操作流程中「必須通過才能繼續」的關卡，以及成功/失敗/不確定三條路徑的設計責任">gate&lt;/a> 和其他 gate 的差異在於狀態的連續性。生物辨識是二元結果（通過或不通過），網路狀態是連續的 — 連線中、已連線、斷線、重新連線、連線但延遲高、連線但頻繁斷開。處理策略取決於功能對即時連線的依賴程度。&lt;/p>
&lt;h2 id="三種處理策略">三種處理策略&lt;/h2>
&lt;h3 id="offline-first">Offline-first&lt;/h3>
&lt;p>功能的核心操作在本地完成，網路用於同步。斷線時使用者仍可操作，重新連線後自動同步差異。&lt;/p>
&lt;p>Offline-first 適合的前提是資料可以本地存儲且衝突可以解決。筆記 app、待辦事項、表單填寫 — 使用者的操作產生本地資料，網路只負責把資料同步到 server。&lt;/p>
&lt;p>Offline-first 的 UX 設計重點是讓使用者知道同步狀態：已同步、待同步、同步失敗。不需要 gate — 網路狀態不阻擋使用者操作。&lt;/p>
&lt;h3 id="retry-with-feedback">Retry with feedback&lt;/h3>
&lt;p>功能需要網路但可以等待。斷線時顯示狀態和重試選項，使用者決定要等還是離開。&lt;/p>
&lt;p>app_tunnel 的 terminal 連線屬於這個模式。WebSocket 連線需要網路，斷線時使用者無法操作終端機。error 和 disconnected 狀態提供重連按鈕讓使用者手動重試。&lt;/p>
&lt;p>Retry 策略的 UX 設計重點：&lt;/p>
&lt;ul>
&lt;li>告知使用者發生什麼（「連線中斷」而非空白畫面）&lt;/li>
&lt;li>提供手動重試（重連按鈕）&lt;/li>
&lt;li>提供退出路徑（返回首頁 — app_tunnel 原本缺少這個）&lt;/li>
&lt;li>自動重試要有上限和間隔遞增（避免無限重試消耗電量）&lt;/li>
&lt;/ul>
&lt;h3 id="degraded-mode">Degraded mode&lt;/h3>
&lt;p>功能部分依賴網路。核心功能離線可用，進階功能需要網路。斷線時自動切換到降級模式，不阻擋使用者操作但功能受限。&lt;/p>
&lt;p>降級模式的 UX 設計重點是清楚標示哪些功能可用、哪些不可用。「離線模式 — 搜尋功能暫時不可用」比靜默隱藏搜尋按鈕更透明。&lt;/p>
&lt;h2 id="網路狀態的-ui-呈現">網路狀態的 UI 呈現&lt;/h2>
&lt;h3 id="全域指示器">全域指示器&lt;/h3>
&lt;p>在 app 頂部或狀態列顯示「離線」標示。適合網路狀態影響全域功能的 app。&lt;/p>
&lt;h3 id="功能級指示器">功能級指示器&lt;/h3>
&lt;p>在需要網路的功能旁邊顯示不可用狀態。適合只有部分功能依賴網路的 app。&lt;/p>
&lt;h3 id="非侵入式通知">非侵入式通知&lt;/h3>
&lt;p>用 Snackbar 或 Toast 短暫顯示「已恢復連線」或「網路中斷」。適合網路狀態偶爾變化的場景。不適合頻繁斷開重連的場景（通知太多會干擾使用者）。&lt;/p>
&lt;h2 id="連線但品質差的場景">連線但品質差的場景&lt;/h2>
&lt;p>網路存在但延遲高或頻繁斷開，比完全離線更難處理。完全離線時 app 可以立即切換到離線模式；連線不穩定時，每次請求可能成功也可能逾時，使用者體驗是「有時候行有時候不行」。&lt;/p>
&lt;p>處理策略：&lt;/p>
&lt;ul>
&lt;li>設定合理的逾時時間（太短會把慢回應判定為失敗，太長讓使用者等太久）&lt;/li>
&lt;li>逾時後顯示狀態和重試選項，不自動重試（避免在不穩定網路上累積重試）&lt;/li>
&lt;li>在 loading 狀態提供取消選項，讓使用者可以中斷等待&lt;/li>
&lt;/ul>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>Gate 設計的通用方法論 → &lt;a href="https://tarrragon.github.io/blog/ux-design/02-gate-fallback/gate-three-questions/" data-link-title="Gate 分類與三問設計法" data-link-desc="每個 gate 設計時問三個問題：成功時做什麼、失敗時做什麼、使用者不知道發生什麼時做什麼">Gate 分類與三問設計法&lt;/a>&lt;/li>
&lt;li>權限請求的 UX 設計 → &lt;a href="https://tarrragon.github.io/blog/ux-design/02-gate-fallback/permission-request-timing/" data-link-title="Permission 請求時機與措辭" data-link-desc="系統權限請求的時機選擇（首次開啟 vs 功能使用時）和說明文字的設計 — 使用者只有一次機會理解為什麼需要這個權限">Permission 請求時機與措辭&lt;/a>&lt;/li>
&lt;li>畫面狀態矩陣中的網路狀態 → &lt;a href="https://tarrragon.github.io/blog/ux-design/01-screen-state-machine/" data-link-title="模組一：畫面狀態機設計" data-link-desc="畫面狀態矩陣（顯示 / 操作 / 進入 / 退出）— 退出路徑為空 = UX 死胡同">ux-design 模組一 畫面狀態機&lt;/a>&lt;/li>
&lt;li>Server 端背壓如何影響 client UX → &lt;a href="https://tarrragon.github.io/blog/devops/03-traffic-management/backpressure/" data-link-title="背壓機制" data-link-desc="下游處理慢時上游怎麼減速 — 有限 buffer &amp;#43; 回壓訊號的設計、和 rate limit 的區別">DevOps 背壓機制&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>網路 <a href="/blog/ux-design/knowledge-cards/gate/" data-link-title="Gate（UX）" data-link-desc="說明使用者操作流程中「必須通過才能繼續」的關卡，以及成功/失敗/不確定三條路徑的設計責任">gate</a> 和其他 gate 的差異在於狀態的連續性。生物辨識是二元結果（通過或不通過），網路狀態是連續的 — 連線中、已連線、斷線、重新連線、連線但延遲高、連線但頻繁斷開。處理策略取決於功能對即時連線的依賴程度。</p>
<h2 id="三種處理策略">三種處理策略</h2>
<h3 id="offline-first">Offline-first</h3>
<p>功能的核心操作在本地完成，網路用於同步。斷線時使用者仍可操作，重新連線後自動同步差異。</p>
<p>Offline-first 適合的前提是資料可以本地存儲且衝突可以解決。筆記 app、待辦事項、表單填寫 — 使用者的操作產生本地資料，網路只負責把資料同步到 server。</p>
<p>Offline-first 的 UX 設計重點是讓使用者知道同步狀態：已同步、待同步、同步失敗。不需要 gate — 網路狀態不阻擋使用者操作。</p>
<h3 id="retry-with-feedback">Retry with feedback</h3>
<p>功能需要網路但可以等待。斷線時顯示狀態和重試選項，使用者決定要等還是離開。</p>
<p>app_tunnel 的 terminal 連線屬於這個模式。WebSocket 連線需要網路，斷線時使用者無法操作終端機。error 和 disconnected 狀態提供重連按鈕讓使用者手動重試。</p>
<p>Retry 策略的 UX 設計重點：</p>
<ul>
<li>告知使用者發生什麼（「連線中斷」而非空白畫面）</li>
<li>提供手動重試（重連按鈕）</li>
<li>提供退出路徑（返回首頁 — app_tunnel 原本缺少這個）</li>
<li>自動重試要有上限和間隔遞增（避免無限重試消耗電量）</li>
</ul>
<h3 id="degraded-mode">Degraded mode</h3>
<p>功能部分依賴網路。核心功能離線可用，進階功能需要網路。斷線時自動切換到降級模式，不阻擋使用者操作但功能受限。</p>
<p>降級模式的 UX 設計重點是清楚標示哪些功能可用、哪些不可用。「離線模式 — 搜尋功能暫時不可用」比靜默隱藏搜尋按鈕更透明。</p>
<h2 id="網路狀態的-ui-呈現">網路狀態的 UI 呈現</h2>
<h3 id="全域指示器">全域指示器</h3>
<p>在 app 頂部或狀態列顯示「離線」標示。適合網路狀態影響全域功能的 app。</p>
<h3 id="功能級指示器">功能級指示器</h3>
<p>在需要網路的功能旁邊顯示不可用狀態。適合只有部分功能依賴網路的 app。</p>
<h3 id="非侵入式通知">非侵入式通知</h3>
<p>用 Snackbar 或 Toast 短暫顯示「已恢復連線」或「網路中斷」。適合網路狀態偶爾變化的場景。不適合頻繁斷開重連的場景（通知太多會干擾使用者）。</p>
<h2 id="連線但品質差的場景">連線但品質差的場景</h2>
<p>網路存在但延遲高或頻繁斷開，比完全離線更難處理。完全離線時 app 可以立即切換到離線模式；連線不穩定時，每次請求可能成功也可能逾時，使用者體驗是「有時候行有時候不行」。</p>
<p>處理策略：</p>
<ul>
<li>設定合理的逾時時間（太短會把慢回應判定為失敗，太長讓使用者等太久）</li>
<li>逾時後顯示狀態和重試選項，不自動重試（避免在不穩定網路上累積重試）</li>
<li>在 loading 狀態提供取消選項，讓使用者可以中斷等待</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Gate 設計的通用方法論 → <a href="/blog/ux-design/02-gate-fallback/gate-three-questions/" data-link-title="Gate 分類與三問設計法" data-link-desc="每個 gate 設計時問三個問題：成功時做什麼、失敗時做什麼、使用者不知道發生什麼時做什麼">Gate 分類與三問設計法</a></li>
<li>權限請求的 UX 設計 → <a href="/blog/ux-design/02-gate-fallback/permission-request-timing/" data-link-title="Permission 請求時機與措辭" data-link-desc="系統權限請求的時機選擇（首次開啟 vs 功能使用時）和說明文字的設計 — 使用者只有一次機會理解為什麼需要這個權限">Permission 請求時機與措辭</a></li>
<li>畫面狀態矩陣中的網路狀態 → <a href="/blog/ux-design/01-screen-state-machine/" data-link-title="模組一：畫面狀態機設計" data-link-desc="畫面狀態矩陣（顯示 / 操作 / 進入 / 退出）— 退出路徑為空 = UX 死胡同">ux-design 模組一 畫面狀態機</a></li>
<li>Server 端背壓如何影響 client UX → <a href="/blog/devops/03-traffic-management/backpressure/" data-link-title="背壓機制" data-link-desc="下游處理慢時上游怎麼減速 — 有限 buffer &#43; 回壓訊號的設計、和 rate limit 的區別">DevOps 背壓機制</a></li>
</ul>
]]></content:encoded></item></channel></rss>