<?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>Search on Tarragon</title><link>https://tarrragon.github.io/blog/tags/search/</link><description>Recent content in Search 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/search/index.xml" rel="self" type="application/rss+xml"/><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>搜尋引擎的匹配模式跟使用者預期的對齊</title><link>https://tarrragon.github.io/blog/report/search-engine-matching-mode-mismatch/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/search-engine-matching-mode-mismatch/</guid><description>&lt;h2 id="核心原則">核心原則&lt;/h2>
&lt;p>&lt;strong>搜尋引擎的「匹配模式」是個經常被忽略的維度&lt;/strong> — 工具的預設行為跟使用者的 mental model 不對齊時、產生 silent 失敗：使用者打字、看不到預期結果、誤以為「沒有」、不會 report bug。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>匹配模式&lt;/th>
 &lt;th>例：query「pre」會匹配&lt;/th>
 &lt;th>典型來源&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Exact&lt;/td>
 &lt;td>&lt;code>pre&lt;/code>（不含「pre」這個 token）&lt;/td>
 &lt;td>DB &lt;code>=&lt;/code> 比較&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Prefix&lt;/td>
 &lt;td>&lt;code>pre&lt;/code>、&lt;code>prefix&lt;/code>、&lt;code>prefetch&lt;/code>、&lt;code>presence&lt;/code>&lt;/td>
 &lt;td>Pagefind / Lunr 預設&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Substring&lt;/td>
 &lt;td>上面 + &lt;code>backpressure&lt;/code>、&lt;code>SuperPress&lt;/code>&lt;/td>
 &lt;td>DB &lt;code>LIKE '%pre%'&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Fuzzy&lt;/td>
 &lt;td>上面 + &lt;code>prv&lt;/code>、&lt;code>pre1&lt;/code>（編輯距離）&lt;/td>
 &lt;td>Algolia、TypeSense&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Semantic&lt;/td>
 &lt;td>上面 + &lt;code>before&lt;/code>、&lt;code>prior&lt;/code>（語意相近）&lt;/td>
 &lt;td>Vector search / LLM&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>使用者被 Google / 桌面搜尋訓練、預期 &lt;strong>substring 或更高層級&lt;/strong>。預設拿到 prefix 的 site search → 「pre」找不到 backpressure → 看起來像 bug 但其實是 capability 落差。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼預設是-prefix">為什麼預設是 prefix&lt;/h2>
&lt;p>Static site search engines（Pagefind / Lunr / MiniSearch 預設）選 prefix matching 的原因：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>因素&lt;/th>
 &lt;th>Prefix&lt;/th>
 &lt;th>Substring&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Index size&lt;/td>
 &lt;td>O(N)&lt;/td>
 &lt;td>O(N²)（要 index 所有後綴）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Query speed&lt;/td>
 &lt;td>快（trie）&lt;/td>
 &lt;td>慢（全掃 substring）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跨語言支援&lt;/td>
 &lt;td>容易&lt;/td>
 &lt;td>中文 / CJK 邊界不明確&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Build time&lt;/td>
 &lt;td>快&lt;/td>
 &lt;td>慢&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>對 static site（沒 server）、index 是要下載到 client 的 — substring index 可能 5-10x 大、unacceptable。Pagefind / Lunr 選 prefix 是「對齊 size constraint」、不是「對齊使用者意圖」。&lt;/p>
&lt;p>這是個典型的 &lt;a href="../ease-of-writing-vs-intent-alignment/">#67 寫作便利度跟意圖對齊反相關&lt;/a> — 工具預設是「實作便利位置」、不是「使用者意圖位置」。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼這個-gap-是-silent">為什麼這個 gap 是 silent&lt;/h2>
&lt;p>跟 &lt;a href="../view-layer-filter-vs-source-layer/">#55 Filter × Source 層錯位&lt;/a> 共用結構：使用者打字看到結果列表、結果不空、看起來「有東西」、不會懷疑 engine 沒在做完整 search。&lt;/p>
&lt;p>silent 失敗的條件：&lt;/p>
&lt;ol>
&lt;li>Prefix matching 對某些 query 仍能回到結果（排版上看起來「有用」）&lt;/li>
&lt;li>使用者不知道「沒看到的還有什麼」&lt;/li>
&lt;li>只有當 query 剛好不是任何 token 的 prefix、才會 0 結果（極少見、這時才會懷疑）&lt;/li>
&lt;/ol>
&lt;p>對照 &lt;a href="../view-layer-filter-vs-source-layer/">#55 silent 失敗條件&lt;/a> 三條件 — 完全一樣的結構：「有部分結果掩蓋了缺口」。&lt;/p>
&lt;hr>
&lt;h2 id="多面向跨工具的匹配模式對照">多面向：跨工具的匹配模式對照&lt;/h2>
&lt;h3 id="前端-client-side-search">前端 client-side search&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>Pagefind v1.5&lt;/td>
 &lt;td>Word-prefix&lt;/td>
 &lt;td>Exact only（&lt;code>useExact&lt;/code>）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Lunr&lt;/td>
 &lt;td>Stem + prefix&lt;/td>
 &lt;td>Wildcard（&lt;code>q+'*'&lt;/code>）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>MiniSearch&lt;/td>
 &lt;td>Prefix&lt;/td>
 &lt;td>Substring（&lt;code>prefix: false, fuzzy: 0.2&lt;/code>）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>FlexSearch&lt;/td>
 &lt;td>Token-based&lt;/td>
 &lt;td>多種 tokenizer（含 ngram）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Fuse.js&lt;/td>
 &lt;td>Fuzzy&lt;/td>
 &lt;td>可關掉 fuzzy 變 substring&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="backend--db">Backend / DB&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>匹配模式&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>SQL &lt;code>=&lt;/code>&lt;/td>
 &lt;td>Exact&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SQL &lt;code>LIKE '%X%'&lt;/code>&lt;/td>
 &lt;td>Substring（O(n) scan）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SQL FULLTEXT&lt;/td>
 &lt;td>Token + stem + (有時 prefix)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ElasticSearch&lt;/td>
 &lt;td>配置：term / match / wildcard / fuzzy / regexp&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>PostgreSQL trigram&lt;/td>
 &lt;td>Substring + similarity&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Vector DB（Pinecone 等）&lt;/td>
 &lt;td>Semantic&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="命令列--ide-搜尋">命令列 / IDE 搜尋&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>預設&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>grep&lt;/code>&lt;/td>
 &lt;td>Substring（regex）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>rg&lt;/code>&lt;/td>
 &lt;td>Substring（smart-case + regex）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Vim &lt;code>/&lt;/code>&lt;/td>
 &lt;td>Regex&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>VSCode 搜尋&lt;/td>
 &lt;td>Substring（含 fuzzy file search）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>共通結構&lt;/strong>：每個工具預設不同、使用者帶著舊工具的 expectation 來、不對齊時 silent 失敗。&lt;/p></description><content:encoded><![CDATA[<h2 id="核心原則">核心原則</h2>
<p><strong>搜尋引擎的「匹配模式」是個經常被忽略的維度</strong> — 工具的預設行為跟使用者的 mental model 不對齊時、產生 silent 失敗：使用者打字、看不到預期結果、誤以為「沒有」、不會 report bug。</p>
<table>
  <thead>
      <tr>
          <th>匹配模式</th>
          <th>例：query「pre」會匹配</th>
          <th>典型來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Exact</td>
          <td><code>pre</code>（不含「pre」這個 token）</td>
          <td>DB <code>=</code> 比較</td>
      </tr>
      <tr>
          <td>Prefix</td>
          <td><code>pre</code>、<code>prefix</code>、<code>prefetch</code>、<code>presence</code></td>
          <td>Pagefind / Lunr 預設</td>
      </tr>
      <tr>
          <td>Substring</td>
          <td>上面 + <code>backpressure</code>、<code>SuperPress</code></td>
          <td>DB <code>LIKE '%pre%'</code></td>
      </tr>
      <tr>
          <td>Fuzzy</td>
          <td>上面 + <code>prv</code>、<code>pre1</code>（編輯距離）</td>
          <td>Algolia、TypeSense</td>
      </tr>
      <tr>
          <td>Semantic</td>
          <td>上面 + <code>before</code>、<code>prior</code>（語意相近）</td>
          <td>Vector search / LLM</td>
      </tr>
  </tbody>
</table>
<p>使用者被 Google / 桌面搜尋訓練、預期 <strong>substring 或更高層級</strong>。預設拿到 prefix 的 site search → 「pre」找不到 backpressure → 看起來像 bug 但其實是 capability 落差。</p>
<hr>
<h2 id="為什麼預設是-prefix">為什麼預設是 prefix</h2>
<p>Static site search engines（Pagefind / Lunr / MiniSearch 預設）選 prefix matching 的原因：</p>
<table>
  <thead>
      <tr>
          <th>因素</th>
          <th>Prefix</th>
          <th>Substring</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Index size</td>
          <td>O(N)</td>
          <td>O(N²)（要 index 所有後綴）</td>
      </tr>
      <tr>
          <td>Query speed</td>
          <td>快（trie）</td>
          <td>慢（全掃 substring）</td>
      </tr>
      <tr>
          <td>跨語言支援</td>
          <td>容易</td>
          <td>中文 / CJK 邊界不明確</td>
      </tr>
      <tr>
          <td>Build time</td>
          <td>快</td>
          <td>慢</td>
      </tr>
  </tbody>
</table>
<p>對 static site（沒 server）、index 是要下載到 client 的 — substring index 可能 5-10x 大、unacceptable。Pagefind / Lunr 選 prefix 是「對齊 size constraint」、不是「對齊使用者意圖」。</p>
<p>這是個典型的 <a href="../ease-of-writing-vs-intent-alignment/">#67 寫作便利度跟意圖對齊反相關</a> — 工具預設是「實作便利位置」、不是「使用者意圖位置」。</p>
<hr>
<h2 id="為什麼這個-gap-是-silent">為什麼這個 gap 是 silent</h2>
<p>跟 <a href="../view-layer-filter-vs-source-layer/">#55 Filter × Source 層錯位</a> 共用結構：使用者打字看到結果列表、結果不空、看起來「有東西」、不會懷疑 engine 沒在做完整 search。</p>
<p>silent 失敗的條件：</p>
<ol>
<li>Prefix matching 對某些 query 仍能回到結果（排版上看起來「有用」）</li>
<li>使用者不知道「沒看到的還有什麼」</li>
<li>只有當 query 剛好不是任何 token 的 prefix、才會 0 結果（極少見、這時才會懷疑）</li>
</ol>
<p>對照 <a href="../view-layer-filter-vs-source-layer/">#55 silent 失敗條件</a> 三條件 — 完全一樣的結構：「有部分結果掩蓋了缺口」。</p>
<hr>
<h2 id="多面向跨工具的匹配模式對照">多面向：跨工具的匹配模式對照</h2>
<h3 id="前端-client-side-search">前端 client-side search</h3>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>預設匹配模式</th>
          <th>可調整為</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Pagefind v1.5</td>
          <td>Word-prefix</td>
          <td>Exact only（<code>useExact</code>）</td>
      </tr>
      <tr>
          <td>Lunr</td>
          <td>Stem + prefix</td>
          <td>Wildcard（<code>q+'*'</code>）</td>
      </tr>
      <tr>
          <td>MiniSearch</td>
          <td>Prefix</td>
          <td>Substring（<code>prefix: false, fuzzy: 0.2</code>）</td>
      </tr>
      <tr>
          <td>FlexSearch</td>
          <td>Token-based</td>
          <td>多種 tokenizer（含 ngram）</td>
      </tr>
      <tr>
          <td>Fuse.js</td>
          <td>Fuzzy</td>
          <td>可關掉 fuzzy 變 substring</td>
      </tr>
  </tbody>
</table>
<h3 id="backend--db">Backend / DB</h3>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>匹配模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SQL <code>=</code></td>
          <td>Exact</td>
      </tr>
      <tr>
          <td>SQL <code>LIKE '%X%'</code></td>
          <td>Substring（O(n) scan）</td>
      </tr>
      <tr>
          <td>SQL FULLTEXT</td>
          <td>Token + stem + (有時 prefix)</td>
      </tr>
      <tr>
          <td>ElasticSearch</td>
          <td>配置：term / match / wildcard / fuzzy / regexp</td>
      </tr>
      <tr>
          <td>PostgreSQL trigram</td>
          <td>Substring + similarity</td>
      </tr>
      <tr>
          <td>Vector DB（Pinecone 等）</td>
          <td>Semantic</td>
      </tr>
  </tbody>
</table>
<h3 id="命令列--ide-搜尋">命令列 / IDE 搜尋</h3>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>預設</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>grep</code></td>
          <td>Substring（regex）</td>
      </tr>
      <tr>
          <td><code>rg</code></td>
          <td>Substring（smart-case + regex）</td>
      </tr>
      <tr>
          <td>Vim <code>/</code></td>
          <td>Regex</td>
      </tr>
      <tr>
          <td>VSCode 搜尋</td>
          <td>Substring（含 fuzzy file search）</td>
      </tr>
  </tbody>
</table>
<p><strong>共通結構</strong>：每個工具預設不同、使用者帶著舊工具的 expectation 來、不對齊時 silent 失敗。</p>
<hr>
<h2 id="識別三問">識別三問</h2>
<p>寫之前 / debug 時、自問：</p>
<h3 id="1-這個工具的預設匹配模式是什麼">1. 這個工具的預設匹配模式是什麼？</h3>
<p>讀 docs、不要假設。Pagefind docs 寫 &ldquo;Pagefind matches by word prefix&rdquo;。Lunr 文件寫 &ldquo;Lunr does prefix matching by default&rdquo;。 預設不是直覺。</p>
<h3 id="2-使用者預期哪種匹配模式">2. 使用者預期哪種匹配模式？</h3>
<p>使用者被別的工具訓練。使用者基數越大、越接近 Google substring + fuzzy 預期。</p>
<table>
  <thead>
      <tr>
          <th>使用者類型</th>
          <th>預期匹配模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>一般使用者（被 Google 訓練）</td>
          <td>Substring + fuzzy + semantic</td>
      </tr>
      <tr>
          <td>開發者（用 grep / IDE）</td>
          <td>Substring + regex</td>
      </tr>
      <tr>
          <td>資料庫使用者（寫 SQL）</td>
          <td>看你給的 hint</td>
      </tr>
      <tr>
          <td>命令列重度使用者</td>
          <td>預設 regex</td>
      </tr>
  </tbody>
</table>
<h3 id="3-gap-多大是否-silent">3. Gap 多大？是否 silent？</h3>
<p>工具預設 vs 使用者預期不一致時、評估「使用者會在多少 case 中遇到不一致」。</p>
<ul>
<li>Prefix vs Substring：使用者只要打詞中間部分就 silent 失敗、頻率高</li>
<li>Prefix vs Fuzzy：使用者打錯字才會發現、頻率低</li>
<li>Substring vs Semantic：使用者用同義詞才會發現、頻率中</li>
</ul>
<p>頻率高的 gap 必須有對策。</p>
<hr>
<h2 id="五種對策跟-59-filter--source-五策略-同構">五種對策（跟 <a href="../filter-source-composition-strategies/">#59 Filter × Source 五策略</a> 同構）</h2>
<h3 id="a選用支援目標匹配模式的引擎">A：選用支援目標匹配模式的引擎</h3>
<p>Pagefind 不支援 substring → 換 MiniSearch / FlexSearch。Lunr 不支援 fuzzy → 換 FlexSearch / Fuse.js。</p>
<ul>
<li><strong>適合</strong>：早期決策、index size 不是 bottleneck、能接受工程量</li>
<li><strong>代價</strong>：換引擎成本（API 不同、index 重建、UI 重整合）</li>
</ul>
<h3 id="b在-build-time-pre-tokenize增加替代-token">B：在 build time pre-tokenize、增加替代 token</h3>
<p>在 build pipeline 拆字、把 <code>backpressure</code> 加進 search index 的多個 token：<code>back</code> + <code>pressure</code> + <code>backpressure</code> + <code>back-pressure</code>。Pagefind 透過 <code>data-pagefind-meta</code> 或多份 hidden text 注入。</p>
<ul>
<li><strong>適合</strong>：少量已知關鍵詞 / 跨語言邊界（中文）/ 能控 build pipeline</li>
<li><strong>代價</strong>：手動標記、index 變大、新詞要加進清單</li>
</ul>
<h3 id="cclient-side-fallback-substring-search">C：Client-side fallback substring search</h3>
<p>Pagefind 找不到時、fetch 一份頁面 metadata（title + slug）、做 client-side substring filter。</p>
<ul>
<li><strong>適合</strong>：頁面數量 &lt; 10000、可接受第二層延遲</li>
<li><strong>代價</strong>：需要額外 fetch + 客戶端 substring scan、兩種 result UI 整合</li>
</ul>
<h3 id="dux-hint-明示匹配模式">D：UX hint 明示匹配模式</h3>
<p>把限制告訴使用者：「搜尋為前綴匹配、想找 X 請打 Y」。對應 <a href="../pattern-explicit-semantic-narrowing/">#66 明示語意縮小</a>。</p>
<ul>
<li><strong>適合</strong>：成本最低、只需文字 hint</li>
<li><strong>代價</strong>：使用者要學新規則、不對齊 Google expectation</li>
</ul>
<h3 id="e接受限制不告知">E：接受限制（不告知）</h3>
<p>不做任何處理、silent 接受。這是反模式（同 <a href="../view-layer-filter-vs-source-layer/">#55 silent 失敗</a>）— 使用者誤以為「沒有相關內容」、放棄。</p>
<hr>
<h2 id="跟其他抽象層原則的關係">跟其他抽象層原則的關係</h2>
<table>
  <thead>
      <tr>
          <th>原則</th>
          <th>跟本卡的關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="../view-layer-filter-vs-source-layer/">#55 Filter × Source 層錯位</a></td>
          <td>都是「使用者意圖跟工具實際行為的 silent gap」、本卡是 matching 維度的展現</td>
      </tr>
      <tr>
          <td><a href="../data-source-shape-defines-feature-shape/">#63 資料源的形狀</a></td>
          <td>形狀是 source 的 capability 維度、本卡是「matching mode」這個 capability 維度</td>
      </tr>
      <tr>
          <td><a href="../ease-of-writing-vs-intent-alignment/">#67 寫作便利度跟意圖對齊反相關</a></td>
          <td>工具預設是實作便利、使用者預期是 mental model 對齊、反相關</td>
      </tr>
      <tr>
          <td><a href="../verification-timeline-checkpoints/">#68 驗收的時間軸</a> Checkpoint 1</td>
          <td>「source capabilities 是否對齊使用者預期」屬意圖完整集 — 容易跳過</td>
      </tr>
      <tr>
          <td><a href="../external-trigger-for-high-roi-work/">#72 高 ROI 無外部觸發</a></td>
          <td>「讀 search engine docs 確認 matching mode」沒便利路徑、容易跳過</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="對應的實作篇">對應的實作篇</h2>
<ul>
<li>本 blog 搜尋頁的 Pagefind prefix-match 限制（這次 user 報的 case）</li>
<li>任何用 client-side search 的 SPA / 靜態站</li>
<li>內部 admin tool 的 search box（往往用 SQL <code>LIKE</code> 的 substring、跟使用者 Google 預期反方向）</li>
<li>ElasticSearch 配置時 term vs match query 的選擇</li>
</ul>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>寫 search feature、沒讀工具的 matching mode docs</td>
          <td>跑識別三問、確認預設</td>
      </tr>
      <tr>
          <td>使用者報「我搜 X 找不到、但是有 X」</td>
          <td>多半是 matching mode gap、不是 bug</td>
      </tr>
      <tr>
          <td>使用者打字、結果列表 0 筆、但確實有相關內容</td>
          <td>不對齊的訊號明顯、需要對策</td>
      </tr>
      <tr>
          <td>Search 跨多種使用者（Google trained / dev / DB user）</td>
          <td>Mental model 異質、選擇性高（A/B + C 組合通常需要）</td>
      </tr>
      <tr>
          <td>工具 docs 寫「matches by word prefix」這類字眼</td>
          <td>警訊 — 預設不是 substring</td>
      </tr>
      <tr>
          <td>Pagefind / Lunr / 任何 static site search</td>
          <td>預設 prefix、要主動評估是否符合需求</td>
      </tr>
  </tbody>
</table>
<p><strong>核心原則</strong>：搜尋引擎的匹配模式是個容易被忽略的 capability 維度。工具預設多半是 prefix（為了 index size）、使用者預期多半是 substring 或更高（被 Google 訓練）。沒對齊 = silent 失敗：使用者誤以為內容不存在、不會 report bug。Checkpoint 1 列「使用者意圖完整集」要包含「使用者打字行為的預期」。</p>
]]></content:encoded></item><item><title>搜尋</title><link>https://tarrragon.github.io/blog/search/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/search/</guid><description/><content:encoded></content:encoded></item></channel></rss>