<?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>Input on Tarragon</title><link>https://tarrragon.github.io/blog/tags/input/</link><description>Recent content in Input 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/input/index.xml" rel="self" type="application/rss+xml"/><item><title>輸入機制決策表</title><link>https://tarrragon.github.io/blog/ux-design/03-input-mechanism/four-dimension-decision/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/03-input-mechanism/four-dimension-decision/</guid><description>&lt;p>輸入機制是設計產物，在功能規格階段決定，和 API schema、畫面狀態矩陣同級。手機鍵盤的行為由多個參數控制，每個參數都是一個設計決策，影響使用者體驗、UI layout 和通訊協議。&lt;/p>
&lt;h2 id="四個決策維度">四個決策維度&lt;/h2>
&lt;h3 id="keyboard-type顯示哪種鍵盤">Keyboard type：顯示哪種鍵盤&lt;/h3>
&lt;p>Keyboard type 決定使用者按下輸入框時出現什麼鍵盤。數字鍵盤、email 鍵盤、URL 鍵盤、一般文字鍵盤 — 每種鍵盤的按鍵配置和自動行為不同。&lt;/p>
&lt;p>選擇判斷依據是「使用者要輸入什麼內容」。email 地址用 email 鍵盤（有 &lt;code>@&lt;/code> 鍵），電話號碼用數字鍵盤，密碼或 CLI 指令用 &lt;code>visiblePassword&lt;/code> 型別（避免自動校正和建議）。&lt;/p>
&lt;p>app_tunnel 的 terminal 輸入框用 &lt;code>TextInputType.visiblePassword&lt;/code> — 因為 CLI 指令包含路徑分隔符、flag 縮寫等非自然語言內容，一般文字鍵盤會嘗試自動校正 &lt;code>ls -la&lt;/code> 或 &lt;code>/usr/bin/&lt;/code> 成其他東西（&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &amp;#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3&lt;/a>）。&lt;/p>
&lt;h3 id="submit-model怎麼送出輸入">Submit model：怎麼送出輸入&lt;/h3>
&lt;p>Submit model 決定使用者輸入的內容何時傳送給系統。兩個基本選項：整行送出（使用者按 Enter/Send 後一次傳送整行）和逐字元送出（每個按鍵即時傳送）。&lt;/p>
&lt;p>這個決策直接影響通訊協議設計（本章合成，UF-8 Derive）。整行送出代表每次傳送一個完整指令字串（&lt;code>ls -la\n&lt;/code>），server 端按行處理。逐字元送出代表每個按鍵產生一個 WebSocket frame（&lt;code>l&lt;/code>、&lt;code>s&lt;/code>、&lt;code> &lt;/code>、&lt;code>-&lt;/code>、&lt;code>l&lt;/code>、&lt;code>a&lt;/code>），server 端需要處理單字元輸入，包括 Tab 補全和 Ctrl+C 這類立即回應的操作。&lt;/p>
&lt;p>app_tunnel 選擇整行送出（&lt;code>onSubmitted&lt;/code>），代表 Tab 補全在 client 端無法觸發（因為 Tab 不會單獨送出），但實作成本較低且協議設計較簡單。逐字元送出支援 Tab 補全和命令編輯，但 protocol 複雜度顯著提高。&lt;/p>
&lt;h3 id="ime-policy輸入法的行為控制">IME policy：輸入法的行為控制&lt;/h3>
&lt;p>IME（Input Method Editor）policy 控制手機輸入法的自動行為：自動校正、建議列、個人化學習。每個行為在某些輸入場景是幫助，在另一些場景是干擾或安全風險。&lt;/p>
&lt;p>三個控制項各自有獨立的影響：&lt;/p>
&lt;ul>
&lt;li>&lt;code>autocorrect&lt;/code>：自動校正把輸入替換成字典中的詞。CLI 指令和路徑不是自然語言，自動校正會破壞輸入內容。&lt;/li>
&lt;li>&lt;code>enableSuggestions&lt;/code>：建議列在鍵盤上方顯示候選詞。在 terminal 場景中建議列遮擋畫面底部的終端機輸出。&lt;/li>
&lt;li>&lt;code>enableIMEPersonalizedLearning&lt;/code>：IME 從使用者輸入中學習新詞，跨 app 適用。CLI 輸入可能包含密碼和路徑 — 這是安全問題，見 &lt;a href="https://tarrragon.github.io/blog/ux-design/03-input-mechanism/ime-security-checklist/" data-link-title="安全敏感輸入框的 IME 控制 checklist" data-link-desc="處理密碼、API key、伺服器路徑等 secret 的輸入框需要關閉 IME 的個人化學習和自動校正 — 安全要求而非 UX 偏好">安全敏感輸入框的 IME 控制 checklist&lt;/a>。&lt;/li>
&lt;/ul>
&lt;h3 id="special-keys特殊按鍵的處理">Special keys：特殊按鍵的處理&lt;/h3>
&lt;p>手機鍵盤沒有桌面鍵盤的 Esc、Tab、Ctrl、方向鍵。如果應用需要這些按鍵，必須自建 UI 元件提供。&lt;/p>
&lt;p>app_tunnel 用底部工具列提供 Esc/Tab/Ctrl/方向鍵。這個工具列的設計（按鈕大小、排列、長按行為）是 UX 決策，不是實作細節。&lt;/p>
&lt;h2 id="決策表作為設計產物">決策表作為設計產物&lt;/h2>
&lt;p>四個維度的決策應該在功能規格中以表格形式記錄，讓 code review 時可以逐項對照實作是否符合規格。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>選項&lt;/th>
 &lt;th>理由&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Keyboard&lt;/td>
 &lt;td>visiblePassword&lt;/td>
 &lt;td>CLI 指令不適用自動校正&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Submit&lt;/td>
 &lt;td>整行送出&lt;/td>
 &lt;td>protocol 簡單，犧牲 Tab 補全&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>IME&lt;/td>
 &lt;td>全關&lt;/td>
 &lt;td>安全考量 + 非自然語言輸入&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Special keys&lt;/td>
 &lt;td>底部工具列&lt;/td>
 &lt;td>手機無實體 Esc/Tab/Ctrl&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>app_tunnel 的六個 TextField 參數全是 W2 hotfix 事後補上的，沒有一個是事前規劃。每個參數都有 gotcha — 漏掉 &lt;code>enableIMEPersonalizedLearning: false&lt;/code> 就是安全漏洞，漏掉 &lt;code>autocorrect: false&lt;/code> 就是 UX 問題。事先決策並記錄在規格中，code review 時逐項勾選，比事後逐一發現問題的成本低。&lt;/p>
&lt;p>四個維度在不同場景下的具體決策各有不同。CLI 場景的特殊需求見 &lt;a href="https://tarrragon.github.io/blog/ux-design/03-input-mechanism/terminal-input-design/" data-link-title="Terminal app 輸入設計" data-link-desc="CLI 場景在手機上的特殊需求 — 非自然語言輸入、特殊按鍵需求、整行 vs 逐字元送出對 protocol 的影響">Terminal app 輸入設計&lt;/a>，安全敏感欄位的 IME 控制逐項列在 &lt;a href="https://tarrragon.github.io/blog/ux-design/03-input-mechanism/ime-security-checklist/" data-link-title="安全敏感輸入框的 IME 控制 checklist" data-link-desc="處理密碼、API key、伺服器路徑等 secret 的輸入框需要關閉 IME 的個人化學習和自動校正 — 安全要求而非 UX 偏好">IME 安全 checklist&lt;/a>。Submit model 的選擇（整行 vs 逐字元）直接影響通訊協議的設計 — 這個交叉影響在 &lt;a href="https://tarrragon.github.io/blog/testing/03-protocol-integration-test/" data-link-title="模組三：協議整合測試" data-link-desc="對真實服務驗證 WebSocket / gRPC / HTTP 協議契約 — unit test 和 E2E test 之間的一層">testing 模組三 協議整合測試&lt;/a>中從 test 的角度分析。&lt;/p></description><content:encoded><![CDATA[<p>輸入機制是設計產物，在功能規格階段決定，和 API schema、畫面狀態矩陣同級。手機鍵盤的行為由多個參數控制，每個參數都是一個設計決策，影響使用者體驗、UI layout 和通訊協議。</p>
<h2 id="四個決策維度">四個決策維度</h2>
<h3 id="keyboard-type顯示哪種鍵盤">Keyboard type：顯示哪種鍵盤</h3>
<p>Keyboard type 決定使用者按下輸入框時出現什麼鍵盤。數字鍵盤、email 鍵盤、URL 鍵盤、一般文字鍵盤 — 每種鍵盤的按鍵配置和自動行為不同。</p>
<p>選擇判斷依據是「使用者要輸入什麼內容」。email 地址用 email 鍵盤（有 <code>@</code> 鍵），電話號碼用數字鍵盤，密碼或 CLI 指令用 <code>visiblePassword</code> 型別（避免自動校正和建議）。</p>
<p>app_tunnel 的 terminal 輸入框用 <code>TextInputType.visiblePassword</code> — 因為 CLI 指令包含路徑分隔符、flag 縮寫等非自然語言內容，一般文字鍵盤會嘗試自動校正 <code>ls -la</code> 或 <code>/usr/bin/</code> 成其他東西（<a href="/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3</a>）。</p>
<h3 id="submit-model怎麼送出輸入">Submit model：怎麼送出輸入</h3>
<p>Submit model 決定使用者輸入的內容何時傳送給系統。兩個基本選項：整行送出（使用者按 Enter/Send 後一次傳送整行）和逐字元送出（每個按鍵即時傳送）。</p>
<p>這個決策直接影響通訊協議設計（本章合成，UF-8 Derive）。整行送出代表每次傳送一個完整指令字串（<code>ls -la\n</code>），server 端按行處理。逐字元送出代表每個按鍵產生一個 WebSocket frame（<code>l</code>、<code>s</code>、<code> </code>、<code>-</code>、<code>l</code>、<code>a</code>），server 端需要處理單字元輸入，包括 Tab 補全和 Ctrl+C 這類立即回應的操作。</p>
<p>app_tunnel 選擇整行送出（<code>onSubmitted</code>），代表 Tab 補全在 client 端無法觸發（因為 Tab 不會單獨送出），但實作成本較低且協議設計較簡單。逐字元送出支援 Tab 補全和命令編輯，但 protocol 複雜度顯著提高。</p>
<h3 id="ime-policy輸入法的行為控制">IME policy：輸入法的行為控制</h3>
<p>IME（Input Method Editor）policy 控制手機輸入法的自動行為：自動校正、建議列、個人化學習。每個行為在某些輸入場景是幫助，在另一些場景是干擾或安全風險。</p>
<p>三個控制項各自有獨立的影響：</p>
<ul>
<li><code>autocorrect</code>：自動校正把輸入替換成字典中的詞。CLI 指令和路徑不是自然語言，自動校正會破壞輸入內容。</li>
<li><code>enableSuggestions</code>：建議列在鍵盤上方顯示候選詞。在 terminal 場景中建議列遮擋畫面底部的終端機輸出。</li>
<li><code>enableIMEPersonalizedLearning</code>：IME 從使用者輸入中學習新詞，跨 app 適用。CLI 輸入可能包含密碼和路徑 — 這是安全問題，見 <a href="/blog/ux-design/03-input-mechanism/ime-security-checklist/" data-link-title="安全敏感輸入框的 IME 控制 checklist" data-link-desc="處理密碼、API key、伺服器路徑等 secret 的輸入框需要關閉 IME 的個人化學習和自動校正 — 安全要求而非 UX 偏好">安全敏感輸入框的 IME 控制 checklist</a>。</li>
</ul>
<h3 id="special-keys特殊按鍵的處理">Special keys：特殊按鍵的處理</h3>
<p>手機鍵盤沒有桌面鍵盤的 Esc、Tab、Ctrl、方向鍵。如果應用需要這些按鍵，必須自建 UI 元件提供。</p>
<p>app_tunnel 用底部工具列提供 Esc/Tab/Ctrl/方向鍵。這個工具列的設計（按鈕大小、排列、長按行為）是 UX 決策，不是實作細節。</p>
<h2 id="決策表作為設計產物">決策表作為設計產物</h2>
<p>四個維度的決策應該在功能規格中以表格形式記錄，讓 code review 時可以逐項對照實作是否符合規格。</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>選項</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Keyboard</td>
          <td>visiblePassword</td>
          <td>CLI 指令不適用自動校正</td>
      </tr>
      <tr>
          <td>Submit</td>
          <td>整行送出</td>
          <td>protocol 簡單，犧牲 Tab 補全</td>
      </tr>
      <tr>
          <td>IME</td>
          <td>全關</td>
          <td>安全考量 + 非自然語言輸入</td>
      </tr>
      <tr>
          <td>Special keys</td>
          <td>底部工具列</td>
          <td>手機無實體 Esc/Tab/Ctrl</td>
      </tr>
  </tbody>
</table>
<p>app_tunnel 的六個 TextField 參數全是 W2 hotfix 事後補上的，沒有一個是事前規劃。每個參數都有 gotcha — 漏掉 <code>enableIMEPersonalizedLearning: false</code> 就是安全漏洞，漏掉 <code>autocorrect: false</code> 就是 UX 問題。事先決策並記錄在規格中，code review 時逐項勾選，比事後逐一發現問題的成本低。</p>
<p>四個維度在不同場景下的具體決策各有不同。CLI 場景的特殊需求見 <a href="/blog/ux-design/03-input-mechanism/terminal-input-design/" data-link-title="Terminal app 輸入設計" data-link-desc="CLI 場景在手機上的特殊需求 — 非自然語言輸入、特殊按鍵需求、整行 vs 逐字元送出對 protocol 的影響">Terminal app 輸入設計</a>，安全敏感欄位的 IME 控制逐項列在 <a href="/blog/ux-design/03-input-mechanism/ime-security-checklist/" data-link-title="安全敏感輸入框的 IME 控制 checklist" data-link-desc="處理密碼、API key、伺服器路徑等 secret 的輸入框需要關閉 IME 的個人化學習和自動校正 — 安全要求而非 UX 偏好">IME 安全 checklist</a>。Submit model 的選擇（整行 vs 逐字元）直接影響通訊協議的設計 — 這個交叉影響在 <a href="/blog/testing/03-protocol-integration-test/" data-link-title="模組三：協議整合測試" data-link-desc="對真實服務驗證 WebSocket / gRPC / HTTP 協議契約 — unit test 和 E2E test 之間的一層">testing 模組三 協議整合測試</a>中從 test 的角度分析。</p>
]]></content:encoded></item><item><title>Terminal app 輸入設計</title><link>https://tarrragon.github.io/blog/ux-design/03-input-mechanism/terminal-input-design/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/03-input-mechanism/terminal-input-design/</guid><description>&lt;p>Terminal app 在手機上的輸入需求和一般文字輸入有根本差異。CLI 指令是結構化語法，路徑分隔符、flag 縮寫、管線符號都有精確語意 — 手機鍵盤為自然語言設計的自動行為（校正、建議、學習）在 CLI 場景中全部變成干擾。&lt;/p>
&lt;h2 id="cli-輸入的特殊性">CLI 輸入的特殊性&lt;/h2>
&lt;p>桌面終端機的鍵盤直接傳送按鍵事件，沒有中間的輸入法處理層。使用者按 &lt;code>l&lt;/code> 就是 &lt;code>l&lt;/code>，按 Tab 就是 Tab，按 Ctrl+C 就是 interrupt signal。&lt;/p>
&lt;p>手機鍵盤在使用者和 app 之間插入了 IME 層。使用者按 &lt;code>l&lt;/code> 時，IME 可能等待後續按鍵組合成完整詞彙再傳送；使用者按的按鍵可能被自動校正替換；使用者的輸入被記錄到 IME 詞庫供跨 app 學習。&lt;/p>
&lt;p>Terminal app 需要繞過或控制 IME 層的這些行為。app_tunnel 的 TextField 用 &lt;code>TextInputType.visiblePassword&lt;/code> + &lt;code>autocorrect: false&lt;/code> + &lt;code>enableSuggestions: false&lt;/code> + &lt;code>enableIMEPersonalizedLearning: false&lt;/code> 四個參數關閉 IME 的自動行為（&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &amp;#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3&lt;/a>）。&lt;/p>
&lt;h2 id="整行送出-vs-逐字元protocol-層的影響">整行送出 vs 逐字元：protocol 層的影響&lt;/h2>
&lt;p>整行送出和逐字元送出在 UI 層看起來只是「按 Enter 送出整行」和「每個按鍵即時送出」的差別，但在 protocol 層是兩種不同的通訊模式。&lt;/p>
&lt;h3 id="整行送出">整行送出&lt;/h3>
&lt;p>Client 端累積使用者輸入，使用者按 Enter 時傳送完整指令字串加換行符（&lt;code>ls -la\n&lt;/code>）。Server 端收到完整行後處理。&lt;/p>
&lt;p>Protocol 設計簡單：每個 WebSocket frame 是一個完整指令。Server 不需要管理部分輸入的狀態，也不需要即時回應 Tab 或方向鍵。&lt;/p>
&lt;p>代價：使用者無法在手機上使用 Tab 補全（Tab 被 IME 攔截或不存在）、無法用方向鍵在指令中移動游標（移動的是 TextField 的游標，不是 server 端的 readline 游標）。&lt;/p>
&lt;h3 id="逐字元送出">逐字元送出&lt;/h3>
&lt;p>Client 端每個按鍵即時傳送一個 WebSocket frame。Server 端的 shell 即時處理每個字元，包括 Tab 補全（server 回傳補全結果）、Ctrl+C（server 中斷當前程序）、方向鍵（server 端 readline 移動游標）。&lt;/p>
&lt;p>Protocol 設計複雜：每個按鍵一個 frame，frame 內容是單一字元或控制序列。Server 端必須維護 readline 狀態。Client 端必須正確編碼控制字元（Ctrl+C = 0x03, Tab = 0x09）。&lt;/p>
&lt;p>代價：protocol 複雜度高，每個按鍵都有網路延遲。在高延遲網路上輸入體驗差（打字後要等 round-trip 才看到回顯）。&lt;/p>
&lt;h3 id="決策在-protocol-層做">決策在 protocol 層做&lt;/h3>
&lt;p>app_tunnel 選擇整行送出，犧牲 Tab 補全換取簡單的 protocol 設計。這個決策應該在 protocol spec 階段做 — 因為它影響 server 端（ttyd）的行為預期和 client 端的 frame 格式。在 UI 實作時才臨時決定，可能和 server 端的行為預期不一致。&lt;/p>
&lt;h2 id="特殊按鍵的-ui-方案">特殊按鍵的 UI 方案&lt;/h2>
&lt;p>手機沒有 Esc、Tab、Ctrl、方向鍵。Terminal app 需要額外的 UI 元件提供這些按鍵。&lt;/p>
&lt;h3 id="底部工具列">底部工具列&lt;/h3>
&lt;p>固定在鍵盤上方的一排按鈕，提供常用特殊鍵。app_tunnel 的工具列包含 Esc、Tab、Ctrl、四個方向鍵。&lt;/p>
&lt;p>工具列的設計考量：按鈕大小（手指能精確觸碰的最小尺寸約 44x44 pt）、排列順序（最常用的放中間）、長按行為（長按 Ctrl 是否支援 Ctrl 組合鍵）。&lt;/p>
&lt;h3 id="ctrl-組合鍵">Ctrl 組合鍵&lt;/h3>
&lt;p>Ctrl+C（中斷）、Ctrl+D（EOF）、Ctrl+Z（暫停）在 CLI 操作中頻繁使用。手機上的實作方式通常是：按下 Ctrl 按鈕後進入「Ctrl 模式」，下一個按鍵自動加 Ctrl 前綴。&lt;/p></description><content:encoded><![CDATA[<p>Terminal app 在手機上的輸入需求和一般文字輸入有根本差異。CLI 指令是結構化語法，路徑分隔符、flag 縮寫、管線符號都有精確語意 — 手機鍵盤為自然語言設計的自動行為（校正、建議、學習）在 CLI 場景中全部變成干擾。</p>
<h2 id="cli-輸入的特殊性">CLI 輸入的特殊性</h2>
<p>桌面終端機的鍵盤直接傳送按鍵事件，沒有中間的輸入法處理層。使用者按 <code>l</code> 就是 <code>l</code>，按 Tab 就是 Tab，按 Ctrl+C 就是 interrupt signal。</p>
<p>手機鍵盤在使用者和 app 之間插入了 IME 層。使用者按 <code>l</code> 時，IME 可能等待後續按鍵組合成完整詞彙再傳送；使用者按的按鍵可能被自動校正替換；使用者的輸入被記錄到 IME 詞庫供跨 app 學習。</p>
<p>Terminal app 需要繞過或控制 IME 層的這些行為。app_tunnel 的 TextField 用 <code>TextInputType.visiblePassword</code> + <code>autocorrect: false</code> + <code>enableSuggestions: false</code> + <code>enableIMEPersonalizedLearning: false</code> 四個參數關閉 IME 的自動行為（<a href="/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3</a>）。</p>
<h2 id="整行送出-vs-逐字元protocol-層的影響">整行送出 vs 逐字元：protocol 層的影響</h2>
<p>整行送出和逐字元送出在 UI 層看起來只是「按 Enter 送出整行」和「每個按鍵即時送出」的差別，但在 protocol 層是兩種不同的通訊模式。</p>
<h3 id="整行送出">整行送出</h3>
<p>Client 端累積使用者輸入，使用者按 Enter 時傳送完整指令字串加換行符（<code>ls -la\n</code>）。Server 端收到完整行後處理。</p>
<p>Protocol 設計簡單：每個 WebSocket frame 是一個完整指令。Server 不需要管理部分輸入的狀態，也不需要即時回應 Tab 或方向鍵。</p>
<p>代價：使用者無法在手機上使用 Tab 補全（Tab 被 IME 攔截或不存在）、無法用方向鍵在指令中移動游標（移動的是 TextField 的游標，不是 server 端的 readline 游標）。</p>
<h3 id="逐字元送出">逐字元送出</h3>
<p>Client 端每個按鍵即時傳送一個 WebSocket frame。Server 端的 shell 即時處理每個字元，包括 Tab 補全（server 回傳補全結果）、Ctrl+C（server 中斷當前程序）、方向鍵（server 端 readline 移動游標）。</p>
<p>Protocol 設計複雜：每個按鍵一個 frame，frame 內容是單一字元或控制序列。Server 端必須維護 readline 狀態。Client 端必須正確編碼控制字元（Ctrl+C = 0x03, Tab = 0x09）。</p>
<p>代價：protocol 複雜度高，每個按鍵都有網路延遲。在高延遲網路上輸入體驗差（打字後要等 round-trip 才看到回顯）。</p>
<h3 id="決策在-protocol-層做">決策在 protocol 層做</h3>
<p>app_tunnel 選擇整行送出，犧牲 Tab 補全換取簡單的 protocol 設計。這個決策應該在 protocol spec 階段做 — 因為它影響 server 端（ttyd）的行為預期和 client 端的 frame 格式。在 UI 實作時才臨時決定，可能和 server 端的行為預期不一致。</p>
<h2 id="特殊按鍵的-ui-方案">特殊按鍵的 UI 方案</h2>
<p>手機沒有 Esc、Tab、Ctrl、方向鍵。Terminal app 需要額外的 UI 元件提供這些按鍵。</p>
<h3 id="底部工具列">底部工具列</h3>
<p>固定在鍵盤上方的一排按鈕，提供常用特殊鍵。app_tunnel 的工具列包含 Esc、Tab、Ctrl、四個方向鍵。</p>
<p>工具列的設計考量：按鈕大小（手指能精確觸碰的最小尺寸約 44x44 pt）、排列順序（最常用的放中間）、長按行為（長按 Ctrl 是否支援 Ctrl 組合鍵）。</p>
<h3 id="ctrl-組合鍵">Ctrl 組合鍵</h3>
<p>Ctrl+C（中斷）、Ctrl+D（EOF）、Ctrl+Z（暫停）在 CLI 操作中頻繁使用。手機上的實作方式通常是：按下 Ctrl 按鈕後進入「Ctrl 模式」，下一個按鍵自動加 Ctrl 前綴。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>四維度決策表 → <a href="/blog/ux-design/03-input-mechanism/four-dimension-decision/" data-link-title="輸入機制決策表" data-link-desc="Keyboard type / submit model / IME policy / special keys 四個維度的決策框架 — 每個維度都是設計決策，影響 UI layout 和 protocol">輸入機制決策表</a></li>
<li>安全敏感輸入框的 IME 控制 → <a href="/blog/ux-design/03-input-mechanism/ime-security-checklist/" data-link-title="安全敏感輸入框的 IME 控制 checklist" data-link-desc="處理密碼、API key、伺服器路徑等 secret 的輸入框需要關閉 IME 的個人化學習和自動校正 — 安全要求而非 UX 偏好">IME 安全 checklist</a></li>
<li>表單場景的輸入設計 → <a href="/blog/ux-design/03-input-mechanism/form-ux-pattern/" data-link-title="表單 UX 模式" data-link-desc="表單輸入的驗證時機、auto-fill 支援、錯誤回饋設計 — 和 terminal 輸入的決策維度相同但選項不同">表單 UX 模式</a></li>
</ul>
]]></content:encoded></item><item><title>U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField</title><link>https://tarrragon.github.io/blog/ux-design/cases/terminal-input-mechanism-absent/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/cases/terminal-input-mechanism-absent/</guid><description>&lt;p>這個案例的核心責任是說明輸入機制是設計產物（在企劃階段決定），不是實作細節（在寫 code 時順便加）。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>app_tunnel 的 Terminal 畫面在 W2 修復前沒有任何文字輸入元件。使用者只能透過底部工具列的特殊鍵（Esc/Tab/Ctrl/方向鍵）操作終端機，無法打字。&lt;/p>
&lt;p>W2-001 修復時加入的 &lt;code>TextField&lt;/code> 及其參數：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">TextField&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="nl">keyboardType:&lt;/span> &lt;span class="n">TextInputType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">visiblePassword&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 避免自動校正
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nl">enableSuggestions:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 關閉建議列
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nl">autocorrect:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 關閉自動校正
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nl">enableIMEPersonalizedLearning:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 關閉 IME 個人化學習
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nl">onSubmitted:&lt;/span> &lt;span class="n">_submitInput&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// Enter 送出整行
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nl">textInputAction:&lt;/span> &lt;span class="n">TextInputAction&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">send&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 鍵盤顯示「傳送」
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每個參數都是一個設計決策，但沒有一個是事前規劃的 — 全部是寫 code 時臨時判斷。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>設計決策&lt;/th>
 &lt;th>事前規劃&lt;/th>
 &lt;th>事後 hotfix 的風險&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>visiblePassword&lt;/code>&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;td>如果用預設 &lt;code>text&lt;/code>，iOS 會自動校正 &lt;code>ls -la&lt;/code> 成其他東西&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>enableSuggestions: false&lt;/code>&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;td>建議列遮擋終端機畫面下方&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>autocorrect: false&lt;/code>&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;td>路徑 &lt;code>/usr/bin/&lt;/code> 可能被校正&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>enableIMEPersonalizedLearning: false&lt;/code>&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;td>CLI 輸入含密碼和路徑，IME 學習是安全風險&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>onSubmitted&lt;/code>（整行送出）&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;td>如果逐字元送出，Tab 補全和命令編輯需要完全不同的 protocol 設計&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>TextInputAction.send&lt;/code>&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;td>如果用 &lt;code>newline&lt;/code>，使用者按 Enter 會換行不送出&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>輸入設計影響 UI layout 和 protocol&lt;/strong>。&lt;code>onSubmitted&lt;/code>（整行送出）vs 逐字元即時送出不只是 UI 問題 — 整行送出代表 protocol 層送的是 &lt;code>command\n&lt;/code>，逐字元送出代表每個按鍵都是一個 WS frame。這個決策應該在 protocol spec 階段就做，因為它影響 server 端的行為預期。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>IME 控制有安全意涵&lt;/strong>。&lt;code>enableIMEPersonalizedLearning: false&lt;/code> 不只是 UX 偏好 — CLI 輸入可能包含資料庫密碼、API key、伺服器路徑。IME 學習這些內容等於把 secret 存到了 IME 的詞庫裡，跨 app 可用。這是安全問題，不是 UX 問題。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>事後 hotfix 的六個參數每個都有 gotcha&lt;/strong>。如果這些決策在企劃階段做，可以寫成決策表並在 code review 時對照。事後 hotfix 時開發者可能漏掉其中一兩個（例如只加 &lt;code>autocorrect: false&lt;/code> 但忘了 &lt;code>enableIMEPersonalizedLearning: false&lt;/code>），漏掉的那個就成為安全漏洞。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>功能規格新增「輸入機制決策表」&lt;/strong>：keyboard type / submit model / IME policy / special keys 四個維度，每個列出選項和取捨理由。&lt;/li>
&lt;li>&lt;strong>輸入機制跟 protocol 一起設計&lt;/strong>：「整行送出」還是「逐字元」決定了 WS 訊框的設計，必須在 protocol spec 階段決定。&lt;/li>
&lt;li>&lt;strong>安全敏感參數強制列入 review checklist&lt;/strong>：&lt;code>enableIMEPersonalizedLearning&lt;/code>、&lt;code>autocorrect&lt;/code> 在處理 secret 的輸入框中是安全要求，不是可選項。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想設計 mobile 輸入機制 → &lt;a href="https://tarrragon.github.io/blog/ux-design/03-input-mechanism/four-dimension-decision/" data-link-title="輸入機制決策表" data-link-desc="Keyboard type / submit model / IME policy / special keys 四個維度的決策框架 — 每個維度都是設計決策，影響 UI layout 和 protocol">輸入機制決策表&lt;/a>&lt;/li>
&lt;li>想看 protocol 跟輸入的關聯 → &lt;a href="https://tarrragon.github.io/blog/testing/cases/ws-text-binary-frame-mock-blindspot/" data-link-title="T.C1 WebSocket text/binary frame 被 FakeWebSocketChannel 遮蔽" data-link-desc="Flutter app 用 Uint8List 發送 WS 資料走 binary frame，ttyd 期望 text frame 靜默忽略 — FakeWebSocketChannel 的 sink.add 接受 dynamic 不區分 frame type，192 個 test 全過但實機無回應">T.C1 WS frame type&lt;/a>（sendData 的型別決策）&lt;/li>
&lt;li>想做安全審查 → 待補：CLI 輸入安全 checklist&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明輸入機制是設計產物（在企劃階段決定），不是實作細節（在寫 code 時順便加）。</p>
<h2 id="觀察">觀察</h2>
<p>app_tunnel 的 Terminal 畫面在 W2 修復前沒有任何文字輸入元件。使用者只能透過底部工具列的特殊鍵（Esc/Tab/Ctrl/方向鍵）操作終端機，無法打字。</p>
<p>W2-001 修復時加入的 <code>TextField</code> 及其參數：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">TextField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nl">keyboardType:</span> <span class="n">TextInputType</span><span class="p">.</span><span class="n">visiblePassword</span><span class="p">,</span>   <span class="c1">// 避免自動校正
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="nl">enableSuggestions:</span> <span class="kc">false</span><span class="p">,</span>                       <span class="c1">// 關閉建議列
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="nl">autocorrect:</span> <span class="kc">false</span><span class="p">,</span>                             <span class="c1">// 關閉自動校正
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>  <span class="nl">enableIMEPersonalizedLearning:</span> <span class="kc">false</span><span class="p">,</span>           <span class="c1">// 關閉 IME 個人化學習
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span>  <span class="nl">onSubmitted:</span> <span class="n">_submitInput</span><span class="p">,</span>                      <span class="c1">// Enter 送出整行
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span>  <span class="nl">textInputAction:</span> <span class="n">TextInputAction</span><span class="p">.</span><span class="n">send</span><span class="p">,</span>          <span class="c1">// 鍵盤顯示「傳送」
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"></span><span class="p">)</span></span></span></code></pre></div><p>每個參數都是一個設計決策，但沒有一個是事前規劃的 — 全部是寫 code 時臨時判斷。</p>
<table>
  <thead>
      <tr>
          <th>設計決策</th>
          <th>事前規劃</th>
          <th>事後 hotfix 的風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>visiblePassword</code></td>
          <td>沒有</td>
          <td>如果用預設 <code>text</code>，iOS 會自動校正 <code>ls -la</code> 成其他東西</td>
      </tr>
      <tr>
          <td><code>enableSuggestions: false</code></td>
          <td>沒有</td>
          <td>建議列遮擋終端機畫面下方</td>
      </tr>
      <tr>
          <td><code>autocorrect: false</code></td>
          <td>沒有</td>
          <td>路徑 <code>/usr/bin/</code> 可能被校正</td>
      </tr>
      <tr>
          <td><code>enableIMEPersonalizedLearning: false</code></td>
          <td>沒有</td>
          <td>CLI 輸入含密碼和路徑，IME 學習是安全風險</td>
      </tr>
      <tr>
          <td><code>onSubmitted</code>（整行送出）</td>
          <td>沒有</td>
          <td>如果逐字元送出，Tab 補全和命令編輯需要完全不同的 protocol 設計</td>
      </tr>
      <tr>
          <td><code>TextInputAction.send</code></td>
          <td>沒有</td>
          <td>如果用 <code>newline</code>，使用者按 Enter 會換行不送出</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>輸入設計影響 UI layout 和 protocol</strong>。<code>onSubmitted</code>（整行送出）vs 逐字元即時送出不只是 UI 問題 — 整行送出代表 protocol 層送的是 <code>command\n</code>，逐字元送出代表每個按鍵都是一個 WS frame。這個決策應該在 protocol spec 階段就做，因為它影響 server 端的行為預期。</p>
</li>
<li>
<p><strong>IME 控制有安全意涵</strong>。<code>enableIMEPersonalizedLearning: false</code> 不只是 UX 偏好 — CLI 輸入可能包含資料庫密碼、API key、伺服器路徑。IME 學習這些內容等於把 secret 存到了 IME 的詞庫裡，跨 app 可用。這是安全問題，不是 UX 問題。</p>
</li>
<li>
<p><strong>事後 hotfix 的六個參數每個都有 gotcha</strong>。如果這些決策在企劃階段做，可以寫成決策表並在 code review 時對照。事後 hotfix 時開發者可能漏掉其中一兩個（例如只加 <code>autocorrect: false</code> 但忘了 <code>enableIMEPersonalizedLearning: false</code>），漏掉的那個就成為安全漏洞。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li><strong>功能規格新增「輸入機制決策表」</strong>：keyboard type / submit model / IME policy / special keys 四個維度，每個列出選項和取捨理由。</li>
<li><strong>輸入機制跟 protocol 一起設計</strong>：「整行送出」還是「逐字元」決定了 WS 訊框的設計，必須在 protocol spec 階段決定。</li>
<li><strong>安全敏感參數強制列入 review checklist</strong>：<code>enableIMEPersonalizedLearning</code>、<code>autocorrect</code> 在處理 secret 的輸入框中是安全要求，不是可選項。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計 mobile 輸入機制 → <a href="/blog/ux-design/03-input-mechanism/four-dimension-decision/" data-link-title="輸入機制決策表" data-link-desc="Keyboard type / submit model / IME policy / special keys 四個維度的決策框架 — 每個維度都是設計決策，影響 UI layout 和 protocol">輸入機制決策表</a></li>
<li>想看 protocol 跟輸入的關聯 → <a href="/blog/testing/cases/ws-text-binary-frame-mock-blindspot/" data-link-title="T.C1 WebSocket text/binary frame 被 FakeWebSocketChannel 遮蔽" data-link-desc="Flutter app 用 Uint8List 發送 WS 資料走 binary frame，ttyd 期望 text frame 靜默忽略 — FakeWebSocketChannel 的 sink.add 接受 dynamic 不區分 frame type，192 個 test 全過但實機無回應">T.C1 WS frame type</a>（sendData 的型別決策）</li>
<li>想做安全審查 → 待補：CLI 輸入安全 checklist</li>
</ul>
]]></content:encoded></item><item><title>表單 UX 模式</title><link>https://tarrragon.github.io/blog/ux-design/03-input-mechanism/form-ux-pattern/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/03-input-mechanism/form-ux-pattern/</guid><description>&lt;p>表單輸入和 terminal 輸入用同一套四維度決策框架（keyboard type / submit model / IME policy / special keys），但每個維度的選項和取捨方向不同。表單場景的使用者輸入的是結構化但自然語言為主的內容 — 姓名、email、地址 — 手機鍵盤的自動行為在這個場景中大部分是幫助。&lt;/p>
&lt;h2 id="keyboard-type-的選擇">Keyboard type 的選擇&lt;/h2>
&lt;p>表單的每個欄位應該使用最適合該欄位內容的鍵盤。正確的 keyboard type 減少使用者在鍵盤上找按鍵的時間，也讓自動填入和驗證更準確。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>欄位&lt;/th>
 &lt;th>Keyboard type&lt;/th>
 &lt;th>理由&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Email&lt;/td>
 &lt;td>emailAddress&lt;/td>
 &lt;td>有 &lt;code>@&lt;/code> 和 &lt;code>.&lt;/code> 快捷鍵&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>電話號碼&lt;/td>
 &lt;td>phone&lt;/td>
 &lt;td>只顯示數字和 &lt;code>+&lt;/code> &lt;code>-&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>URL&lt;/td>
 &lt;td>url&lt;/td>
 &lt;td>有 &lt;code>.com&lt;/code> 快捷鍵和 &lt;code>/&lt;/code> 鍵&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>密碼&lt;/td>
 &lt;td>visiblePassword&lt;/td>
 &lt;td>關閉自動校正，保留字元可見控制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>搜尋&lt;/td>
 &lt;td>text&lt;/td>
 &lt;td>一般文字，可搭配 &lt;code>textInputAction: search&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>數字金額&lt;/td>
 &lt;td>numberWithOptions(decimal: true)&lt;/td>
 &lt;td>數字鍵盤加小數點&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="驗證時機">驗證時機&lt;/h2>
&lt;p>表單驗證的時機影響使用者的操作流暢度和錯誤修正效率。&lt;/p>
&lt;h3 id="即時驗證on-change">即時驗證（on change）&lt;/h3>
&lt;p>每次輸入變化時驗證。適合格式明確的欄位（email 格式、手機號碼長度）。即時驗證在使用者輸入過程中就能回饋格式錯誤，不需要等到送出。&lt;/p>
&lt;p>即時驗證的風險是過早報錯。使用者正在輸入 email 地址 &lt;code>user@&lt;/code> 時，缺少 domain 部分 — 這個時候報「email 格式錯誤」對使用者沒有幫助。解法是在欄位失去焦點（on blur）時才顯示錯誤，輸入過程中只顯示通過的驗證（例如勾號表示格式正確）。&lt;/p>
&lt;h3 id="送出時驗證on-submit">送出時驗證（on submit）&lt;/h3>
&lt;p>使用者按送出按鈕時統一驗證所有欄位。適合需要多欄位交叉驗證的場景（密碼確認、日期範圍）。&lt;/p>
&lt;p>送出時驗證的風險是使用者填完整張表單才知道哪裡有問題。在欄位多的表單中，使用者需要回頭找到錯誤欄位修正 — 用 scroll 定位和欄位 highlight 減輕這個成本。&lt;/p>
&lt;h3 id="混合策略">混合策略&lt;/h3>
&lt;p>格式驗證用即時（on blur）、業務邏輯驗證用送出時。Email 格式在失去焦點時檢查，「email 是否已被註冊」在送出時呼叫 API 檢查。&lt;/p>
&lt;h2 id="auto-fill-支援">Auto-fill 支援&lt;/h2>
&lt;p>手機系統（iOS AutoFill、Android Autofill）可以自動填入使用者已儲存的資訊（姓名、地址、信用卡）。App 需要正確標記每個欄位的語意類型（&lt;code>autofillHints&lt;/code>），系統才能匹配正確的儲存值。&lt;/p>
&lt;p>正確標記的欄位讓使用者一鍵填入而非手動輸入 — 在 mobile 上減少的打字量直接轉化為轉換率提升。&lt;/p>
&lt;h2 id="錯誤回饋">錯誤回饋&lt;/h2>
&lt;p>錯誤訊息的位置和內容影響使用者修正錯誤的效率。&lt;/p>
&lt;p>&lt;strong>位置&lt;/strong>：錯誤訊息應該緊跟在對應欄位下方，而非集中在表單頂部或底部。使用者需要在看到錯誤的同時看到對應的欄位，不需要來回比對。&lt;/p>
&lt;p>&lt;strong>內容&lt;/strong>：錯誤訊息應該說明「期望什麼」而非「哪裡錯了」。「請輸入有效的 email 地址」比「email 格式無效」提供更多行動指引。&lt;/p>
&lt;p>&lt;strong>視覺&lt;/strong>：錯誤欄位的邊框變色（通常紅色）讓使用者在視覺掃描時快速定位。搭配錯誤文字使用，不要只靠顏色（色盲使用者）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>搜尋場景的輸入設計 → &lt;a href="https://tarrragon.github.io/blog/ux-design/03-input-mechanism/search-ux-pattern/" data-link-title="搜尋 UX 模式" data-link-desc="Debounce / instant / suggestion 三種搜尋模式的取捨 — 和輸入機制的 submit model 維度直接相關">搜尋 UX 模式&lt;/a>&lt;/li>
&lt;li>四維度決策表總覽 → &lt;a href="https://tarrragon.github.io/blog/ux-design/03-input-mechanism/four-dimension-decision/" data-link-title="輸入機制決策表" data-link-desc="Keyboard type / submit model / IME policy / special keys 四個維度的決策框架 — 每個維度都是設計決策，影響 UI layout 和 protocol">輸入機制決策表&lt;/a>&lt;/li>
&lt;li>安全敏感欄位的 IME 控制 → &lt;a href="https://tarrragon.github.io/blog/ux-design/03-input-mechanism/ime-security-checklist/" data-link-title="安全敏感輸入框的 IME 控制 checklist" data-link-desc="處理密碼、API key、伺服器路徑等 secret 的輸入框需要關閉 IME 的個人化學習和自動校正 — 安全要求而非 UX 偏好">IME 安全 checklist&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>表單輸入和 terminal 輸入用同一套四維度決策框架（keyboard type / submit model / IME policy / special keys），但每個維度的選項和取捨方向不同。表單場景的使用者輸入的是結構化但自然語言為主的內容 — 姓名、email、地址 — 手機鍵盤的自動行為在這個場景中大部分是幫助。</p>
<h2 id="keyboard-type-的選擇">Keyboard type 的選擇</h2>
<p>表單的每個欄位應該使用最適合該欄位內容的鍵盤。正確的 keyboard type 減少使用者在鍵盤上找按鍵的時間，也讓自動填入和驗證更準確。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>Keyboard type</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Email</td>
          <td>emailAddress</td>
          <td>有 <code>@</code> 和 <code>.</code> 快捷鍵</td>
      </tr>
      <tr>
          <td>電話號碼</td>
          <td>phone</td>
          <td>只顯示數字和 <code>+</code> <code>-</code></td>
      </tr>
      <tr>
          <td>URL</td>
          <td>url</td>
          <td>有 <code>.com</code> 快捷鍵和 <code>/</code> 鍵</td>
      </tr>
      <tr>
          <td>密碼</td>
          <td>visiblePassword</td>
          <td>關閉自動校正，保留字元可見控制</td>
      </tr>
      <tr>
          <td>搜尋</td>
          <td>text</td>
          <td>一般文字，可搭配 <code>textInputAction: search</code></td>
      </tr>
      <tr>
          <td>數字金額</td>
          <td>numberWithOptions(decimal: true)</td>
          <td>數字鍵盤加小數點</td>
      </tr>
  </tbody>
</table>
<h2 id="驗證時機">驗證時機</h2>
<p>表單驗證的時機影響使用者的操作流暢度和錯誤修正效率。</p>
<h3 id="即時驗證on-change">即時驗證（on change）</h3>
<p>每次輸入變化時驗證。適合格式明確的欄位（email 格式、手機號碼長度）。即時驗證在使用者輸入過程中就能回饋格式錯誤，不需要等到送出。</p>
<p>即時驗證的風險是過早報錯。使用者正在輸入 email 地址 <code>user@</code> 時，缺少 domain 部分 — 這個時候報「email 格式錯誤」對使用者沒有幫助。解法是在欄位失去焦點（on blur）時才顯示錯誤，輸入過程中只顯示通過的驗證（例如勾號表示格式正確）。</p>
<h3 id="送出時驗證on-submit">送出時驗證（on submit）</h3>
<p>使用者按送出按鈕時統一驗證所有欄位。適合需要多欄位交叉驗證的場景（密碼確認、日期範圍）。</p>
<p>送出時驗證的風險是使用者填完整張表單才知道哪裡有問題。在欄位多的表單中，使用者需要回頭找到錯誤欄位修正 — 用 scroll 定位和欄位 highlight 減輕這個成本。</p>
<h3 id="混合策略">混合策略</h3>
<p>格式驗證用即時（on blur）、業務邏輯驗證用送出時。Email 格式在失去焦點時檢查，「email 是否已被註冊」在送出時呼叫 API 檢查。</p>
<h2 id="auto-fill-支援">Auto-fill 支援</h2>
<p>手機系統（iOS AutoFill、Android Autofill）可以自動填入使用者已儲存的資訊（姓名、地址、信用卡）。App 需要正確標記每個欄位的語意類型（<code>autofillHints</code>），系統才能匹配正確的儲存值。</p>
<p>正確標記的欄位讓使用者一鍵填入而非手動輸入 — 在 mobile 上減少的打字量直接轉化為轉換率提升。</p>
<h2 id="錯誤回饋">錯誤回饋</h2>
<p>錯誤訊息的位置和內容影響使用者修正錯誤的效率。</p>
<p><strong>位置</strong>：錯誤訊息應該緊跟在對應欄位下方，而非集中在表單頂部或底部。使用者需要在看到錯誤的同時看到對應的欄位，不需要來回比對。</p>
<p><strong>內容</strong>：錯誤訊息應該說明「期望什麼」而非「哪裡錯了」。「請輸入有效的 email 地址」比「email 格式無效」提供更多行動指引。</p>
<p><strong>視覺</strong>：錯誤欄位的邊框變色（通常紅色）讓使用者在視覺掃描時快速定位。搭配錯誤文字使用，不要只靠顏色（色盲使用者）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>搜尋場景的輸入設計 → <a href="/blog/ux-design/03-input-mechanism/search-ux-pattern/" data-link-title="搜尋 UX 模式" data-link-desc="Debounce / instant / suggestion 三種搜尋模式的取捨 — 和輸入機制的 submit model 維度直接相關">搜尋 UX 模式</a></li>
<li>四維度決策表總覽 → <a href="/blog/ux-design/03-input-mechanism/four-dimension-decision/" data-link-title="輸入機制決策表" data-link-desc="Keyboard type / submit model / IME policy / special keys 四個維度的決策框架 — 每個維度都是設計決策，影響 UI layout 和 protocol">輸入機制決策表</a></li>
<li>安全敏感欄位的 IME 控制 → <a href="/blog/ux-design/03-input-mechanism/ime-security-checklist/" data-link-title="安全敏感輸入框的 IME 控制 checklist" data-link-desc="處理密碼、API key、伺服器路徑等 secret 的輸入框需要關閉 IME 的個人化學習和自動校正 — 安全要求而非 UX 偏好">IME 安全 checklist</a></li>
</ul>
]]></content:encoded></item><item><title>模組三：輸入機制設計</title><link>https://tarrragon.github.io/blog/ux-design/03-input-mechanism/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/03-input-mechanism/</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-6&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &amp;#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3&lt;/a>&lt;/td>
 &lt;td>6 個 TextField 參數全是事後 hotfix — &lt;strong>本模組主寫&lt;/strong>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>UF-7&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &amp;#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3&lt;/a>&lt;/td>
 &lt;td>enableIMEPersonalizedLearning 有安全意涵（secret → IME 詞庫）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>UF-8&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &amp;#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3&lt;/a>&lt;/td>
 &lt;td>整行送出 vs 逐字元影響 protocol 設計 — &lt;strong>SSoT 主寫&lt;/strong>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="待寫章節">待寫章節&lt;/h2>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 輸入機制四維度決策表（keyboard type / submit model / IME policy / special keys）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Terminal app 輸入設計（CLI 特殊需求）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 表單 UX 模式（validate / auto-fill / error feedback）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 搜尋 UX 模式（debounce / instant / suggestion）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 安全敏感輸入框的 IME 控制 checklist&lt;/li>
&lt;/ul>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/testing/03-protocol-integration-test/" data-link-title="模組三：協議整合測試" data-link-desc="對真實服務驗證 WebSocket / gRPC / HTTP 協議契約 — unit test 和 E2E test 之間的一層">testing 模組三 協議整合測試&lt;/a>：整行 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>：IME 個人化學習 = secret 洩漏風險&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-6</td>
          <td><a href="/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3</a></td>
          <td>6 個 TextField 參數全是事後 hotfix — <strong>本模組主寫</strong></td>
      </tr>
      <tr>
          <td>UF-7</td>
          <td><a href="/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3</a></td>
          <td>enableIMEPersonalizedLearning 有安全意涵（secret → IME 詞庫）</td>
      </tr>
      <tr>
          <td>UF-8</td>
          <td><a href="/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3</a></td>
          <td>整行送出 vs 逐字元影響 protocol 設計 — <strong>SSoT 主寫</strong></td>
      </tr>
  </tbody>
</table>
<h2 id="待寫章節">待寫章節</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> 輸入機制四維度決策表（keyboard type / submit model / IME policy / special keys）</li>
<li><input checked="" disabled="" type="checkbox"> Terminal app 輸入設計（CLI 特殊需求）</li>
<li><input checked="" disabled="" type="checkbox"> 表單 UX 模式（validate / auto-fill / error feedback）</li>
<li><input checked="" disabled="" type="checkbox"> 搜尋 UX 模式（debounce / instant / suggestion）</li>
<li><input checked="" disabled="" type="checkbox"> 安全敏感輸入框的 IME 控制 checklist</li>
</ul>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/testing/03-protocol-integration-test/" data-link-title="模組三：協議整合測試" data-link-desc="對真實服務驗證 WebSocket / gRPC / HTTP 協議契約 — unit test 和 E2E test 之間的一層">testing 模組三 協議整合測試</a>：整行 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>：IME 個人化學習 = secret 洩漏風險</li>
</ul>
]]></content:encoded></item><item><title>搜尋 UX 模式</title><link>https://tarrragon.github.io/blog/ux-design/03-input-mechanism/search-ux-pattern/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/03-input-mechanism/search-ux-pattern/</guid><description>&lt;p>搜尋輸入的核心決策是「使用者輸入到什麼程度觸發搜尋」。這和 terminal 輸入的 submit model 維度相同 — 差別在 terminal 場景的選項是「整行 vs 逐字元」，搜尋場景的選項是「按送出 vs 即時 vs debounce」。&lt;/p>
&lt;h2 id="三種觸發模式">三種觸發模式&lt;/h2>
&lt;h3 id="按送出觸發">按送出觸發&lt;/h3>
&lt;p>使用者打完搜尋詞、按搜尋按鈕後觸發一次搜尋。最簡單的模式 — 一次搜尋、一次 API 呼叫、一次結果顯示。&lt;/p>
&lt;p>適合搜尋成本高的場景：資料庫全文搜尋、外部 API 呼叫（有速率限制或費用）、搜尋結果需要複雜運算。&lt;/p>
&lt;h3 id="即時觸發instant">即時觸發（instant）&lt;/h3>
&lt;p>使用者每輸入一個字元就觸發搜尋。結果即時更新，使用者可以在輸入過程中看到搜尋結果逐漸精確。&lt;/p>
&lt;p>適合搜尋成本低的場景：client 端的本地過濾、記憶體內的資料集篩選、已快取的少量資料。&lt;/p>
&lt;p>即時觸發在搜尋成本高的場景會產生問題：使用者輸入 &lt;code>hello&lt;/code> 的過程中觸發五次 API 呼叫（&lt;code>h&lt;/code>、&lt;code>he&lt;/code>、&lt;code>hel&lt;/code>、&lt;code>hell&lt;/code>、&lt;code>hello&lt;/code>），前四次的結果在使用者看到之前就被覆蓋。浪費的 API 呼叫增加 server 負載和使用者的網路流量。&lt;/p>
&lt;h3 id="debounce-觸發">Debounce 觸發&lt;/h3>
&lt;p>使用者停止輸入一段時間後（通常 300-500ms）觸發搜尋。平衡即時回饋和 API 呼叫次數 — 使用者連續打字時不觸發，停下來時觸發一次。&lt;/p>
&lt;p>Debounce 是遠端搜尋場景的常見選擇。延遲時間的設定是 UX trade-off：太短（100ms）接近即時觸發，API 呼叫次數多；太長（1000ms）使用者感覺到明顯延遲。300-500ms 是多數場景的合理區間。&lt;/p>
&lt;h2 id="搜尋結果的顯示">搜尋結果的顯示&lt;/h2>
&lt;h3 id="suggestion-list建議列表">Suggestion list（建議列表）&lt;/h3>
&lt;p>在搜尋框下方即時顯示候選結果。使用者可以點選候選項完成搜尋，不需要打完整個搜尋詞。&lt;/p>
&lt;p>Suggestion list 適合搜尋詞有限且可列舉的場景（城市名、產品名、使用者名）。搜尋詞無限（全文搜尋）時 suggestion list 的候選項品質依賴搜尋演算法。&lt;/p>
&lt;h3 id="結果頁">結果頁&lt;/h3>
&lt;p>使用者送出搜尋後導航到獨立的結果頁面。適合結果量大、需要分頁、每筆結果需要較多空間展示的場景。&lt;/p>
&lt;h3 id="即時過濾filter">即時過濾（filter）&lt;/h3>
&lt;p>在現有列表上即時隱藏不符合搜尋條件的項目，不導航到新頁面。適合「在已經看得到的清單中找到特定項目」的場景。&lt;/p>
&lt;h2 id="keyboard-type-和-textinputaction">keyboard type 和 textInputAction&lt;/h2>
&lt;p>搜尋框的 keyboard type 通常用 &lt;code>text&lt;/code>（一般文字），搭配 &lt;code>textInputAction: search&lt;/code> 讓鍵盤的 Enter 鍵顯示搜尋圖示（放大鏡）而非換行或送出圖示。&lt;/p>
&lt;p>這個細節影響使用者的操作直覺 — 看到搜尋圖示的按鈕，使用者知道按下去會觸發搜尋；看到換行圖示，使用者可能猶豫按下去會不會換行。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>四維度決策表總覽 → &lt;a href="https://tarrragon.github.io/blog/ux-design/03-input-mechanism/four-dimension-decision/" data-link-title="輸入機制決策表" data-link-desc="Keyboard type / submit model / IME policy / special keys 四個維度的決策框架 — 每個維度都是設計決策，影響 UI layout 和 protocol">輸入機制決策表&lt;/a>&lt;/li>
&lt;li>Terminal 場景的 submit model → &lt;a href="https://tarrragon.github.io/blog/ux-design/03-input-mechanism/terminal-input-design/" data-link-title="Terminal app 輸入設計" data-link-desc="CLI 場景在手機上的特殊需求 — 非自然語言輸入、特殊按鍵需求、整行 vs 逐字元送出對 protocol 的影響">Terminal app 輸入設計&lt;/a>&lt;/li>
&lt;li>表單場景的驗證設計 → &lt;a href="https://tarrragon.github.io/blog/ux-design/03-input-mechanism/form-ux-pattern/" data-link-title="表單 UX 模式" data-link-desc="表單輸入的驗證時機、auto-fill 支援、錯誤回饋設計 — 和 terminal 輸入的決策維度相同但選項不同">表單 UX 模式&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>搜尋輸入的核心決策是「使用者輸入到什麼程度觸發搜尋」。這和 terminal 輸入的 submit model 維度相同 — 差別在 terminal 場景的選項是「整行 vs 逐字元」，搜尋場景的選項是「按送出 vs 即時 vs debounce」。</p>
<h2 id="三種觸發模式">三種觸發模式</h2>
<h3 id="按送出觸發">按送出觸發</h3>
<p>使用者打完搜尋詞、按搜尋按鈕後觸發一次搜尋。最簡單的模式 — 一次搜尋、一次 API 呼叫、一次結果顯示。</p>
<p>適合搜尋成本高的場景：資料庫全文搜尋、外部 API 呼叫（有速率限制或費用）、搜尋結果需要複雜運算。</p>
<h3 id="即時觸發instant">即時觸發（instant）</h3>
<p>使用者每輸入一個字元就觸發搜尋。結果即時更新，使用者可以在輸入過程中看到搜尋結果逐漸精確。</p>
<p>適合搜尋成本低的場景：client 端的本地過濾、記憶體內的資料集篩選、已快取的少量資料。</p>
<p>即時觸發在搜尋成本高的場景會產生問題：使用者輸入 <code>hello</code> 的過程中觸發五次 API 呼叫（<code>h</code>、<code>he</code>、<code>hel</code>、<code>hell</code>、<code>hello</code>），前四次的結果在使用者看到之前就被覆蓋。浪費的 API 呼叫增加 server 負載和使用者的網路流量。</p>
<h3 id="debounce-觸發">Debounce 觸發</h3>
<p>使用者停止輸入一段時間後（通常 300-500ms）觸發搜尋。平衡即時回饋和 API 呼叫次數 — 使用者連續打字時不觸發，停下來時觸發一次。</p>
<p>Debounce 是遠端搜尋場景的常見選擇。延遲時間的設定是 UX trade-off：太短（100ms）接近即時觸發，API 呼叫次數多；太長（1000ms）使用者感覺到明顯延遲。300-500ms 是多數場景的合理區間。</p>
<h2 id="搜尋結果的顯示">搜尋結果的顯示</h2>
<h3 id="suggestion-list建議列表">Suggestion list（建議列表）</h3>
<p>在搜尋框下方即時顯示候選結果。使用者可以點選候選項完成搜尋，不需要打完整個搜尋詞。</p>
<p>Suggestion list 適合搜尋詞有限且可列舉的場景（城市名、產品名、使用者名）。搜尋詞無限（全文搜尋）時 suggestion list 的候選項品質依賴搜尋演算法。</p>
<h3 id="結果頁">結果頁</h3>
<p>使用者送出搜尋後導航到獨立的結果頁面。適合結果量大、需要分頁、每筆結果需要較多空間展示的場景。</p>
<h3 id="即時過濾filter">即時過濾（filter）</h3>
<p>在現有列表上即時隱藏不符合搜尋條件的項目，不導航到新頁面。適合「在已經看得到的清單中找到特定項目」的場景。</p>
<h2 id="keyboard-type-和-textinputaction">keyboard type 和 textInputAction</h2>
<p>搜尋框的 keyboard type 通常用 <code>text</code>（一般文字），搭配 <code>textInputAction: search</code> 讓鍵盤的 Enter 鍵顯示搜尋圖示（放大鏡）而非換行或送出圖示。</p>
<p>這個細節影響使用者的操作直覺 — 看到搜尋圖示的按鈕，使用者知道按下去會觸發搜尋；看到換行圖示，使用者可能猶豫按下去會不會換行。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>四維度決策表總覽 → <a href="/blog/ux-design/03-input-mechanism/four-dimension-decision/" data-link-title="輸入機制決策表" data-link-desc="Keyboard type / submit model / IME policy / special keys 四個維度的決策框架 — 每個維度都是設計決策，影響 UI layout 和 protocol">輸入機制決策表</a></li>
<li>Terminal 場景的 submit model → <a href="/blog/ux-design/03-input-mechanism/terminal-input-design/" data-link-title="Terminal app 輸入設計" data-link-desc="CLI 場景在手機上的特殊需求 — 非自然語言輸入、特殊按鍵需求、整行 vs 逐字元送出對 protocol 的影響">Terminal app 輸入設計</a></li>
<li>表單場景的驗證設計 → <a href="/blog/ux-design/03-input-mechanism/form-ux-pattern/" data-link-title="表單 UX 模式" data-link-desc="表單輸入的驗證時機、auto-fill 支援、錯誤回饋設計 — 和 terminal 輸入的決策維度相同但選項不同">表單 UX 模式</a></li>
</ul>
]]></content:encoded></item><item><title>安全敏感輸入框的 IME 控制 checklist</title><link>https://tarrragon.github.io/blog/ux-design/03-input-mechanism/ime-security-checklist/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/03-input-mechanism/ime-security-checklist/</guid><description>&lt;p>IME（Input Method Editor）的個人化學習功能會從使用者輸入中學習新詞彙，存入 IME 詞庫，跨 app 適用。在處理 secret 的輸入框中，這個功能把密碼、API key、伺服器路徑等敏感資訊存入了 IME 的持久化儲存 — 其他 app 的使用者在輸入時可能在建議列表中看到這些內容。&lt;/p>
&lt;h2 id="為什麼是安全問題">為什麼是安全問題&lt;/h2>
&lt;p>&lt;code>enableIMEPersonalizedLearning&lt;/code> 控制的是 IME 是否從當前輸入框的內容學習新詞彙。預設值是 &lt;code>true&lt;/code> — IME 會學習使用者輸入的所有內容。&lt;/p>
&lt;p>在一般文字輸入場景中（聊天、筆記、email），IME 學習使用者的常用詞彙是合理的 — 提高打字效率，減少重複輸入。&lt;/p>
&lt;p>在 CLI 場景中（&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &amp;#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3&lt;/a>），使用者可能輸入：&lt;/p>
&lt;ul>
&lt;li>資料庫密碼：&lt;code>mysql -p'MySecret123'&lt;/code>&lt;/li>
&lt;li>API key：&lt;code>curl -H 'Authorization: Bearer sk-abc123...'&lt;/code>&lt;/li>
&lt;li>伺服器路徑：&lt;code>ssh admin@192.168.1.100&lt;/code>&lt;/li>
&lt;li>環境變數：&lt;code>export DB_PASSWORD=secret&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>IME 學習這些輸入後，使用者在其他 app 打字時，IME 可能在建議列表中顯示 &lt;code>MySecret123&lt;/code> 或 &lt;code>sk-abc123&lt;/code> — 任何看到螢幕的人都能看到。&lt;/p>
&lt;p>這個風險和密碼外洩的傳統路徑不同。傳統密碼外洩通常是資料庫被入侵或傳輸被攔截；IME 學習造成的洩漏是使用者在日常打字時被動暴露，使用者不會意識到 IME 記住了他們在另一個 app 輸入的密碼。&lt;/p>
&lt;h2 id="checklist">Checklist&lt;/h2>
&lt;p>處理以下任何一類內容的輸入框，應全部通過此 checklist：&lt;/p>
&lt;ul>
&lt;li>密碼、PIN 碼&lt;/li>
&lt;li>API key、token、secret&lt;/li>
&lt;li>伺服器位址、連線字串&lt;/li>
&lt;li>CLI 指令（可能包含上述任何一類）&lt;/li>
&lt;li>信用卡號碼&lt;/li>
&lt;li>任何標示為 confidential 的欄位&lt;/li>
&lt;/ul>
&lt;h3 id="必須關閉的-ime-控制">必須關閉的 IME 控制&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &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;code>enableIMEPersonalizedLearning: false&lt;/code>&lt;/td>
 &lt;td>防止 secret 進入 IME 詞庫&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>自動校正&lt;/td>
 &lt;td>&lt;code>autocorrect: false&lt;/code>&lt;/td>
 &lt;td>防止 secret 被替換成字典詞&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>輸入建議&lt;/td>
 &lt;td>&lt;code>enableSuggestions: false&lt;/code>&lt;/td>
 &lt;td>防止 secret 出現在建議列表&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="建議的-keyboard-type">建議的 keyboard type&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>Keyboard type&lt;/th>
 &lt;th>理由&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>密碼&lt;/td>
 &lt;td>visiblePassword&lt;/td>
 &lt;td>關閉自動校正，可選顯示/隱藏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CLI 指令&lt;/td>
 &lt;td>visiblePassword&lt;/td>
 &lt;td>需要精確輸入，不要自動校正&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>信用卡號碼&lt;/td>
 &lt;td>number&lt;/td>
 &lt;td>只需要數字鍵盤&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>連線字串&lt;/td>
 &lt;td>url&lt;/td>
 &lt;td>有 &lt;code>.&lt;/code>、&lt;code>/&lt;/code>、&lt;code>:&lt;/code> 快捷鍵&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="code-review-檢查點">Code review 檢查點&lt;/h3>
&lt;p>Review 安全敏感輸入框的 TextField 實作時，逐項確認：&lt;/p>
&lt;ol>
&lt;li>&lt;code>enableIMEPersonalizedLearning&lt;/code> 是否明確設為 &lt;code>false&lt;/code>（不依賴預設值）&lt;/li>
&lt;li>&lt;code>autocorrect&lt;/code> 是否設為 &lt;code>false&lt;/code>&lt;/li>
&lt;li>&lt;code>enableSuggestions&lt;/code> 是否設為 &lt;code>false&lt;/code>&lt;/li>
&lt;li>&lt;code>keyboardType&lt;/code> 是否選擇了不會觸發自動行為的類型&lt;/li>
&lt;li>如果是密碼欄位，&lt;code>obscureText&lt;/code> 是否按需求設定&lt;/li>
&lt;/ol>
&lt;h2 id="平台差異">平台差異&lt;/h2>
&lt;p>&lt;code>enableIMEPersonalizedLearning&lt;/code> 是 Flutter 的 API，對應到不同平台的不同機制：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>iOS&lt;/strong>：對應 &lt;code>UITextField.spellCheckingType = .no&lt;/code> 和相關 attribute。iOS 的 QuickType 學習機制由系統控制，app 只能建議不強制。&lt;/li>
&lt;li>&lt;strong>Android&lt;/strong>：對應 &lt;code>InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS&lt;/code> 等 flag。不同 IME app（Gboard、Samsung Keyboard、搜狗）對 flag 的遵守程度不一。&lt;/li>
&lt;/ul>
&lt;p>平台差異意味著 app 端的控制是「盡力而為」— 設定正確的 flag 是必要條件，但不保證所有 IME 都會遵守。安全敏感場景中，除了 IME 控制外，還應考慮 secure text entry（&lt;code>obscureText: true&lt;/code>）讓畫面上不顯示明文。&lt;/p></description><content:encoded><![CDATA[<p>IME（Input Method Editor）的個人化學習功能會從使用者輸入中學習新詞彙，存入 IME 詞庫，跨 app 適用。在處理 secret 的輸入框中，這個功能把密碼、API key、伺服器路徑等敏感資訊存入了 IME 的持久化儲存 — 其他 app 的使用者在輸入時可能在建議列表中看到這些內容。</p>
<h2 id="為什麼是安全問題">為什麼是安全問題</h2>
<p><code>enableIMEPersonalizedLearning</code> 控制的是 IME 是否從當前輸入框的內容學習新詞彙。預設值是 <code>true</code> — IME 會學習使用者輸入的所有內容。</p>
<p>在一般文字輸入場景中（聊天、筆記、email），IME 學習使用者的常用詞彙是合理的 — 提高打字效率，減少重複輸入。</p>
<p>在 CLI 場景中（<a href="/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3</a>），使用者可能輸入：</p>
<ul>
<li>資料庫密碼：<code>mysql -p'MySecret123'</code></li>
<li>API key：<code>curl -H 'Authorization: Bearer sk-abc123...'</code></li>
<li>伺服器路徑：<code>ssh admin@192.168.1.100</code></li>
<li>環境變數：<code>export DB_PASSWORD=secret</code></li>
</ul>
<p>IME 學習這些輸入後，使用者在其他 app 打字時，IME 可能在建議列表中顯示 <code>MySecret123</code> 或 <code>sk-abc123</code> — 任何看到螢幕的人都能看到。</p>
<p>這個風險和密碼外洩的傳統路徑不同。傳統密碼外洩通常是資料庫被入侵或傳輸被攔截；IME 學習造成的洩漏是使用者在日常打字時被動暴露，使用者不會意識到 IME 記住了他們在另一個 app 輸入的密碼。</p>
<h2 id="checklist">Checklist</h2>
<p>處理以下任何一類內容的輸入框，應全部通過此 checklist：</p>
<ul>
<li>密碼、PIN 碼</li>
<li>API key、token、secret</li>
<li>伺服器位址、連線字串</li>
<li>CLI 指令（可能包含上述任何一類）</li>
<li>信用卡號碼</li>
<li>任何標示為 confidential 的欄位</li>
</ul>
<h3 id="必須關閉的-ime-控制">必須關閉的 IME 控制</h3>
<table>
  <thead>
      <tr>
          <th>控制項</th>
          <th>參數</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>個人化學習</td>
          <td><code>enableIMEPersonalizedLearning: false</code></td>
          <td>防止 secret 進入 IME 詞庫</td>
      </tr>
      <tr>
          <td>自動校正</td>
          <td><code>autocorrect: false</code></td>
          <td>防止 secret 被替換成字典詞</td>
      </tr>
      <tr>
          <td>輸入建議</td>
          <td><code>enableSuggestions: false</code></td>
          <td>防止 secret 出現在建議列表</td>
      </tr>
  </tbody>
</table>
<h3 id="建議的-keyboard-type">建議的 keyboard type</h3>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>Keyboard type</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>密碼</td>
          <td>visiblePassword</td>
          <td>關閉自動校正，可選顯示/隱藏</td>
      </tr>
      <tr>
          <td>CLI 指令</td>
          <td>visiblePassword</td>
          <td>需要精確輸入，不要自動校正</td>
      </tr>
      <tr>
          <td>信用卡號碼</td>
          <td>number</td>
          <td>只需要數字鍵盤</td>
      </tr>
      <tr>
          <td>連線字串</td>
          <td>url</td>
          <td>有 <code>.</code>、<code>/</code>、<code>:</code> 快捷鍵</td>
      </tr>
  </tbody>
</table>
<h3 id="code-review-檢查點">Code review 檢查點</h3>
<p>Review 安全敏感輸入框的 TextField 實作時，逐項確認：</p>
<ol>
<li><code>enableIMEPersonalizedLearning</code> 是否明確設為 <code>false</code>（不依賴預設值）</li>
<li><code>autocorrect</code> 是否設為 <code>false</code></li>
<li><code>enableSuggestions</code> 是否設為 <code>false</code></li>
<li><code>keyboardType</code> 是否選擇了不會觸發自動行為的類型</li>
<li>如果是密碼欄位，<code>obscureText</code> 是否按需求設定</li>
</ol>
<h2 id="平台差異">平台差異</h2>
<p><code>enableIMEPersonalizedLearning</code> 是 Flutter 的 API，對應到不同平台的不同機制：</p>
<ul>
<li><strong>iOS</strong>：對應 <code>UITextField.spellCheckingType = .no</code> 和相關 attribute。iOS 的 QuickType 學習機制由系統控制，app 只能建議不強制。</li>
<li><strong>Android</strong>：對應 <code>InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS</code> 等 flag。不同 IME app（Gboard、Samsung Keyboard、搜狗）對 flag 的遵守程度不一。</li>
</ul>
<p>平台差異意味著 app 端的控制是「盡力而為」— 設定正確的 flag 是必要條件，但不保證所有 IME 都會遵守。安全敏感場景中，除了 IME 控制外，還應考慮 secure text entry（<code>obscureText: true</code>）讓畫面上不顯示明文。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>四維度決策表總覽 → <a href="/blog/ux-design/03-input-mechanism/four-dimension-decision/" data-link-title="輸入機制決策表" data-link-desc="Keyboard type / submit model / IME policy / special keys 四個維度的決策框架 — 每個維度都是設計決策，影響 UI layout 和 protocol">輸入機制決策表</a></li>
<li>IME 個人化學習在 monitoring 中的安全考量 → <a href="/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">monitoring 模組七 資安</a></li>
<li>Terminal 場景的完整輸入設計 → <a href="/blog/ux-design/03-input-mechanism/terminal-input-design/" data-link-title="Terminal app 輸入設計" data-link-desc="CLI 場景在手機上的特殊需求 — 非自然語言輸入、特殊按鍵需求、整行 vs 逐字元送出對 protocol 的影響">Terminal app 輸入設計</a></li>
</ul>
]]></content:encoded></item></channel></rss>