<?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>對話協議 on Tarragon</title><link>https://tarrragon.github.io/blog/tags/%E5%B0%8D%E8%A9%B1%E5%8D%94%E8%AD%B0/</link><description>Recent content in 對話協議 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Sun, 26 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/%E5%B0%8D%E8%A9%B1%E5%8D%94%E8%AD%B0/index.xml" rel="self" type="application/rss+xml"/><item><title>Clarifying Ambiguous Instructions — 模糊指令澄清協議</title><link>https://tarrragon.github.io/blog/skills/requirement-protocol/clarifying-ambiguous-instructions/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/requirement-protocol/clarifying-ambiguous-instructions/</guid><description>&lt;p>收到含模糊形容詞或缺數字的指令時 — 把指令翻譯成可驗證的具體輸入、給選項讓使用者點頭、再開始實作。&lt;/p>
&lt;p>適用：空間 / 尺寸（「padding 加大」「對齊」）、相對位置（「在 X 旁邊」「靠近 Y」）、隔離（「不要動 X」「跟 Y 分開」）、決定權（「breakpoint 設多少」）、篩選（「依 X 篩選」「只看 Y」）。
不適用：內部技術選擇（grid / flex、observer 種類）— 那些自決即可、見「可決定 vs 該確認」段落。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋五類模糊指令的澄清模板與「自決 vs 確認」的判準。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="何時參閱本文件">何時參閱本文件&lt;/h2>
&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;/td>
 &lt;td>列計算過程、給三個選項&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>指令含「在 X 旁 / 上 / 下 / 之間」&lt;/td>
 &lt;td>用文字畫 ASCII layout 草圖、確認佈局&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>指令含「不要動 X」「跟 Y 分開」「隔離 Z」&lt;/td>
 &lt;td>確認邊界類型（DOM / layout / state）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將寫死一個 breakpoint / 預設值 / 順序 / UI 文字&lt;/td>
 &lt;td>列選項 + 推薦、不要自決&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>指令含「依 X 篩選」「只看 X」「過濾 Y」&lt;/td>
 &lt;td>跑篩選三問（定義域 / 資料源型態 / 空狀態）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>不確定某個決定該自決還是攤給使用者&lt;/td>
 &lt;td>跑「visible 三問」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼模糊指令需要獨立的澄清協議">為什麼模糊指令需要獨立的澄清協議&lt;/h2>
&lt;p>模糊指令的核心問題是&lt;strong>指令的資訊量不足以唯一決定實作&lt;/strong>。執行者若直接寫死、結果可能跟使用者預期不符；使用者只能在看到結果後才能說「不對」、然後重做。&lt;/p>
&lt;p>把澄清拉到實作前 = 用對話成本（給選項 + 推薦）換掉重做成本。但所有事都確認 = 對話爆炸。協議的價值在於分辨「該攤的」與「該自決的」。&lt;/p>
&lt;hr>
&lt;h2 id="visible-三問自決還是確認">「visible 三問」：自決還是確認&lt;/h2>
&lt;p>任一個答「是」 → 該確認；全「否」 → 可自決。&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>Q1：這個決定在 UI 上會產生使用者感知的差異嗎？&lt;/td>
 &lt;td>breakpoint、預設尺寸、初始視覺&lt;/td>
 &lt;td>用 grid 還是 flex 排兩欄&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Q2：選不同會不會影響使用者體驗？&lt;/td>
 &lt;td>filter 排序、選單順序&lt;/td>
 &lt;td>ResizeObserver 還是 setInterval&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Q3：寫進 commit 後改動成本高嗎？&lt;/td>
 &lt;td>section 名稱、URL 結構、檔案命名&lt;/td>
 &lt;td>內部 helper function 名稱&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>確認時的格式&lt;/strong>：給選項 + 推薦 + 開放修改。&lt;/p>
&lt;blockquote>
&lt;p>較差：「Breakpoint 應該設多少？」（開放問題、把分析丟給使用者）&lt;/p>
&lt;p>較好：「Breakpoint 我預估三個選項：1280px / 1400px / 1564px。我會選 1400px、有 120px 餘裕、平衡安全與緊湊。OK 嗎？或要其他？」&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="四類模糊指令的澄清模板">四類模糊指令的澄清模板&lt;/h2>
&lt;h3 id="類型-1空間--尺寸類缺數字">類型 1：空間 / 尺寸類（缺數字）&lt;/h3>
&lt;p>&lt;strong>徵兆&lt;/strong>：「padding 加大」「對齊」「靠近一點」「再窄一些」&lt;/p>
&lt;p>&lt;strong>協議&lt;/strong>：&lt;/p>
&lt;ol>
&lt;li>列計算過程：「Filter sidebar 寬 = main padding + sidebar 寬度 + gap = 16 + 320 + 24 = 360px」&lt;/li>
&lt;li>列假設來源：「H1 高度從 design token 讀（48px）；form 高度量測（72px）；gap 16px 取自 base unit」&lt;/li>
&lt;li>給三檔選項（緊 / 中 / 鬆）+ 推薦&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>反例&lt;/strong>：直接寫 &lt;code>padding: 24px&lt;/code>、沒交代為什麼是 24。使用者只能視覺驗收、不對就重做。&lt;/p></description><content:encoded><![CDATA[<p>收到含模糊形容詞或缺數字的指令時 — 把指令翻譯成可驗證的具體輸入、給選項讓使用者點頭、再開始實作。</p>
<p>適用：空間 / 尺寸（「padding 加大」「對齊」）、相對位置（「在 X 旁邊」「靠近 Y」）、隔離（「不要動 X」「跟 Y 分開」）、決定權（「breakpoint 設多少」）、篩選（「依 X 篩選」「只看 Y」）。
不適用：內部技術選擇（grid / flex、observer 種類）— 那些自決即可、見「可決定 vs 該確認」段落。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋五類模糊指令的澄清模板與「自決 vs 確認」的判準。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>指令含「加大 / 縮小 / 對齊 / 靠近」但沒給數字</td>
          <td>列計算過程、給三個選項</td>
      </tr>
      <tr>
          <td>指令含「在 X 旁 / 上 / 下 / 之間」</td>
          <td>用文字畫 ASCII layout 草圖、確認佈局</td>
      </tr>
      <tr>
          <td>指令含「不要動 X」「跟 Y 分開」「隔離 Z」</td>
          <td>確認邊界類型（DOM / layout / state）</td>
      </tr>
      <tr>
          <td>即將寫死一個 breakpoint / 預設值 / 順序 / UI 文字</td>
          <td>列選項 + 推薦、不要自決</td>
      </tr>
      <tr>
          <td>指令含「依 X 篩選」「只看 X」「過濾 Y」</td>
          <td>跑篩選三問（定義域 / 資料源型態 / 空狀態）</td>
      </tr>
      <tr>
          <td>不確定某個決定該自決還是攤給使用者</td>
          <td>跑「visible 三問」</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼模糊指令需要獨立的澄清協議">為什麼模糊指令需要獨立的澄清協議</h2>
<p>模糊指令的核心問題是<strong>指令的資訊量不足以唯一決定實作</strong>。執行者若直接寫死、結果可能跟使用者預期不符；使用者只能在看到結果後才能說「不對」、然後重做。</p>
<p>把澄清拉到實作前 = 用對話成本（給選項 + 推薦）換掉重做成本。但所有事都確認 = 對話爆炸。協議的價值在於分辨「該攤的」與「該自決的」。</p>
<hr>
<h2 id="visible-三問自決還是確認">「visible 三問」：自決還是確認</h2>
<p>任一個答「是」 → 該確認；全「否」 → 可自決。</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>範例 → 該確認</th>
          <th>範例 → 可自決</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Q1：這個決定在 UI 上會產生使用者感知的差異嗎？</td>
          <td>breakpoint、預設尺寸、初始視覺</td>
          <td>用 grid 還是 flex 排兩欄</td>
      </tr>
      <tr>
          <td>Q2：選不同會不會影響使用者體驗？</td>
          <td>filter 排序、選單順序</td>
          <td>ResizeObserver 還是 setInterval</td>
      </tr>
      <tr>
          <td>Q3：寫進 commit 後改動成本高嗎？</td>
          <td>section 名稱、URL 結構、檔案命名</td>
          <td>內部 helper function 名稱</td>
      </tr>
  </tbody>
</table>
<p><strong>確認時的格式</strong>：給選項 + 推薦 + 開放修改。</p>
<blockquote>
<p>較差：「Breakpoint 應該設多少？」（開放問題、把分析丟給使用者）</p>
<p>較好：「Breakpoint 我預估三個選項：1280px / 1400px / 1564px。我會選 1400px、有 120px 餘裕、平衡安全與緊湊。OK 嗎？或要其他？」</p></blockquote>
<hr>
<h2 id="四類模糊指令的澄清模板">四類模糊指令的澄清模板</h2>
<h3 id="類型-1空間--尺寸類缺數字">類型 1：空間 / 尺寸類（缺數字）</h3>
<p><strong>徵兆</strong>：「padding 加大」「對齊」「靠近一點」「再窄一些」</p>
<p><strong>協議</strong>：</p>
<ol>
<li>列計算過程：「Filter sidebar 寬 = main padding + sidebar 寬度 + gap = 16 + 320 + 24 = 360px」</li>
<li>列假設來源：「H1 高度從 design token 讀（48px）；form 高度量測（72px）；gap 16px 取自 base unit」</li>
<li>給三檔選項（緊 / 中 / 鬆）+ 推薦</li>
</ol>
<p><strong>反例</strong>：直接寫 <code>padding: 24px</code>、沒交代為什麼是 24。使用者只能視覺驗收、不對就重做。</p>
<h3 id="類型-2相對位置類在-x-旁--上--下--之間">類型 2：相對位置類（「在 X 旁 / 上 / 下 / 之間」）</h3>
<p><strong>徵兆</strong>：「scope 放在搜尋框跟結果之間」「filter 在 sidebar」「按鈕靠右」</p>
<p><strong>協議</strong>：</p>
<ol>
<li>用文字 ASCII 畫 layout 草圖、列出 stacking 與 viewport 行為</li>
<li>標出錨點是誰（誰決定其他元素的位置）</li>
<li>給選項對應「不同 layout 邏輯」、不只是位置</li>
</ol>
<p><strong>範例草圖</strong>：</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">方案 A：grid 縱向排
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">┌─────────────┐
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">│  H1         │
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│  Form       │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│  Scope      │  ← 在 form 與 results 之間
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│  Results    │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">└─────────────┘
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">方案 B：absolute 疊在 form 下方
</span></span><span class="line"><span class="ln">10</span><span class="cl">┌─────────────┐
</span></span><span class="line"><span class="ln">11</span><span class="cl">│  H1         │
</span></span><span class="line"><span class="ln">12</span><span class="cl">│  Form ──┐   │
</span></span><span class="line"><span class="ln">13</span><span class="cl">│  [scope abs]│  ← 跳出 flow、Form 給 margin-bottom
</span></span><span class="line"><span class="ln">14</span><span class="cl">│  Results    │
</span></span><span class="line"><span class="ln">15</span><span class="cl">└─────────────┘</span></span></code></pre></div><p>兩個方案 layout 邏輯不同、後續維護成本不同 — 給使用者選、不替使用者選。</p>
<h3 id="類型-3隔離類不要動分開">類型 3：隔離類（「不要動」「分開」）</h3>
<p><strong>徵兆</strong>：「不要動 framework 的 DOM」「跟 X 分開」「隔離 Y」</p>
<p><strong>協議</strong>：先確認「隔離」的邊界類型 — 同一句話可能是四種不同意思：</p>
<table>
  <thead>
      <tr>
          <th>邊界類型</th>
          <th>意思</th>
          <th>實作方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DOM 結構</td>
          <td>不要新增 / 移除 / reparent 節點</td>
          <td>只用 CSS、不用 JS 動 DOM</td>
      </tr>
      <tr>
          <td>Layout flow</td>
          <td>不要影響其他元素的位置</td>
          <td>absolute / fixed 跳出 flow</td>
      </tr>
      <tr>
          <td>State</td>
          <td>不要共用 state、互不影響</td>
          <td>獨立 store / 獨立 component</td>
      </tr>
      <tr>
          <td>Framework 管轄</td>
          <td>不要進入框架重新渲染的子樹</td>
          <td>把客製 UI 注入到框架邊界外</td>
      </tr>
  </tbody>
</table>
<p>確認模板：「您說的『不要動』是哪一層？是不能動 DOM 結構、不能影響 layout、還是不能進 framework 子樹？」</p>
<h3 id="類型-4決定權類應該設多少">類型 4：決定權類（「應該設多少」）</h3>
<p><strong>徵兆</strong>：使用者用問句回拋、或留下沒明說的數值</p>
<p><strong>協議</strong>：使用者沒明示 → 你先給三個選項 + 推薦、再讓使用者選。<strong>使用者已明示數值</strong> → 直接用、不要再問。</p>
<p>差別判斷：訊息裡有數字（即使粗略）→ 已明示；訊息裡只有概念形容（「再大一點」）→ 沒明示。</p>
<h3 id="類型-5篩選類依-x-篩選只看-y">類型 5：篩選類（「依 X 篩選」「只看 Y」）</h3>
<p><strong>徵兆</strong>：「依 X 篩選」「只看 type=post 的」「過濾掉 Y」「title-only 搜尋」</p>
<p>跟前四類的差別：前四類缺幾何 / 邊界 / 拍板資訊（單一維度）、篩選類<strong>缺操作的層級</strong>（filter 該在 view / 渲染 / 資料 / source 哪一層）。沒澄清就寫 = 必然寫成最便利的 view 層 post-filter、撞上資料層分批時的層錯位。</p>
<p><strong>協議</strong>：跑篩選三問。</p>
<h4 id="問-1定義域">問 1：定義域</h4>





<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">「依 X 篩選」是指：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">(a) 在已載入的結果裡找 X 符合的（filter 範圍 = 已抓的子集）
</span></span><span class="line"><span class="ln">4</span><span class="cl">(b) 在所有結果裡找 X 符合的（filter 範圍 = 完整 dataset）
</span></span><span class="line"><span class="ln">5</span><span class="cl">(c) 重新搜尋、把 X 當成 query 條件（filter ≡ 改 query）
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">通常 (b) 是使用者預期、但實作成本看 (c) 是不是 source 支援的。哪一個？</span></span></code></pre></div><h4 id="問-2資料源型態">問 2：資料源型態</h4>





<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">資料源是：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">(a) 一次性給完整 dataset（靜態陣列、一次 fetch 到底）
</span></span><span class="line"><span class="ln">4</span><span class="cl">(b) 分批 / 限額（pagefind、paginated API、infinite scroll）
</span></span><span class="line"><span class="ln">5</span><span class="cl">(c) Streaming（SSE / WebSocket、來多少看多少）
</span></span><span class="line"><span class="ln">6</span><span class="cl">(d) Cached + revalidate（先 cache 後 fresh）
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">(a) 沒有層錯位風險、直接寫 view 層 filter；
</span></span><span class="line"><span class="ln">9</span><span class="cl">(b)(c)(d) 必須跟 source 對齊或加自動續抓 / 誠實 UX。</span></span></code></pre></div><h4 id="問-3空狀態區分">問 3：空狀態區分</h4>





<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">當 filter 後 0 筆顯示、要不要區分：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">(a) 「沒命中」（已抓完、確定 0）
</span></span><span class="line"><span class="ln">4</span><span class="cl">(b) 「還沒抓到」（已載入子集裡 0、source 還有）
</span></span><span class="line"><span class="ln">5</span><span class="cl">(c) 「載入中」（fetch 還在跑）
</span></span><span class="line"><span class="ln">6</span><span class="cl">(d) 「載入失敗」
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">通常都該區分、但實作上能忍受多少混為一談？</span></span></code></pre></div><p>三問的詳細展開（含 5 種篩選形式 / 跨情境範例 / 後端 + 演算法）見 <a href="/blog/report/filter-instruction-clarification/" data-link-title="篩選類指令的澄清時機" data-link-desc="「依 X 篩選」這類指令必須先澄清三件事才能寫：定義域（已載入 / 全部 / 子集）、資料分批方式、空狀態的語意。三問跑完才寫、否則必然寫成視覺層 post-filter、撞上 #55 層錯位。">#58 篩選類指令的澄清時機</a>；解法策略見 <a href="/blog/report/filter-source-composition-strategies/" data-link-title="Filter × Source 的合成策略五選一" data-link-desc="Filter 跟 paginated / streaming source 合成的五種策略、各自機會成本不同：A 推進 query / B 自動續抓 / C 預先 index / D 誠實 UX / E 接受語意縮小。沒有絕對最佳、看 source capabilities、match 密度、UX 容忍度而定。">#59 Filter × Source 合成策略五選一</a>。</p>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1缺數字的指令">範例 1：缺數字的指令</h3>
<blockquote>
<p>使用者：「filter sidebar 太窄了、加寬一些」</p></blockquote>
<p><strong>錯</strong>：直接改 <code>width: 400px</code>、commit、等使用者驗收。</p>
<p><strong>對</strong>：</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">目前 sidebar 是 320px、main padding 是 16px。我列三個選項：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">- 360px：+40、適合多 1 個 facet column
</span></span><span class="line"><span class="ln">4</span><span class="cl">- 400px：+80、舒展、跟 main 1:2.5 比例
</span></span><span class="line"><span class="ln">5</span><span class="cl">- 440px：+120、最寬、main 會被擠到 580px 以下
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">推薦 400px。OK 嗎？或要其他寬度？</span></span></code></pre></div><h3 id="範例-2相對位置">範例 2：相對位置</h3>
<blockquote>
<p>使用者：「scope 放在搜尋框旁邊」</p></blockquote>
<p><strong>錯</strong>：腦補「旁邊 = 右邊」、用 flex row 排出來。</p>
<p><strong>對</strong>：</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">「旁邊」我想到三種佈局：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">A. Form 右側、跟 input 同一列   → flex row、會擠壓 input 寬度
</span></span><span class="line"><span class="ln">4</span><span class="cl">B. Form 上方                     → absolute、不影響 input
</span></span><span class="line"><span class="ln">5</span><span class="cl">C. Form 下方、結果之上           → grid row 排序、跟 results 共生
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">推薦 C。或您想的是哪一種？</span></span></code></pre></div><h3 id="範例-3隔離指令">範例 3：隔離指令</h3>
<blockquote>
<p>使用者：「客製 UI 不要動到 pagefind 的 DOM」</p></blockquote>
<p><strong>錯</strong>：理解成「不要 querySelector 它」、結果 inject 到 framework 子樹。</p>
<p><strong>對</strong>：</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">確認一下「不要動」的邊界 — 是：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">(a) 不能 querySelector 也不能 mutate？
</span></span><span class="line"><span class="ln">4</span><span class="cl">(b) 可以查、不能改 attribute / textContent？
</span></span><span class="line"><span class="ln">5</span><span class="cl">(c) 整節點 reparent 也不行？
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">通常 (b) + (c) 是底線（reparent 安全、attribute 改容易被 reconcile 清掉）。
</span></span><span class="line"><span class="ln">8</span><span class="cl">您的需求是哪一層？</span></span></code></pre></div><hr>
<h2 id="自檢清單dogfooding清單本身遵守它教的規則">自檢清單（dogfooding：清單本身遵守它教的規則）</h2>
<p>寫完澄清訊息、回頭檢查：</p>
<ul>
<li><input disabled="" type="checkbox"> 訊息含數字或具體 layout 結構、不只有形容詞</li>
<li><input disabled="" type="checkbox"> 給了 ≥ 2 個選項、含推薦</li>
<li><input disabled="" type="checkbox"> 選項之間的差異是「使用者體驗 / 維護成本」、不只是「實作細節」</li>
<li><input disabled="" type="checkbox"> 沒有把分析負擔丟回給使用者（沒寫「您覺得呢？」這種開放問）</li>
<li><input disabled="" type="checkbox"> 純技術細節沒攤給使用者煩（grid / flex、selector 寫法）</li>
</ul>
<p>如果 ≥ 2 項打勾失敗、訊息回到草稿重寫。</p>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code> 目錄下）：</p>
<ul>
<li><a href="/blog/report/spatial-instruction-clarification/" data-link-title="空間 / 尺寸類指令的澄清時機" data-link-desc="聽到「對齊 X」「擺在 Y 旁邊」但沒給數字時，先列計算過程讓使用者確認、不直接寫死。本文展開這類指令的處理 protocol。">spatial-instruction-clarification</a> — 空間 / 尺寸類</li>
<li><a href="/blog/report/relative-position-instruction-clarification/" data-link-title="元件相對位置類指令的澄清時機" data-link-desc="聽到「X 在 Y 旁邊」時、先用文字畫個 layout 草圖讓使用者確認、不憑直覺擺。本文展開這類指令的處理 protocol。">relative-position-instruction-clarification</a> — 相對位置類</li>
<li><a href="/blog/report/isolation-instruction-clarification/" data-link-title="隔離程度類指令的澄清時機" data-link-desc="聽到「隔離」「不要動 X」時、先確認邊界是 DOM 結構、layout flow、state、還是 framework 管轄區。本文展開隔離邊界的四種類型與澄清方式。">isolation-instruction-clarification</a> — 隔離類</li>
<li><a href="/blog/report/decide-vs-confirm-boundary/" data-link-title="「可決定」與「該先確認」的邊界" data-link-desc="寫死任何使用者會看到的數字 / 順序 / 文字之前、先給選項讓使用者點頭。技術實作細節可以自決。本文展開兩者的區分原則。">decide-vs-confirm-boundary</a> — 可決定 vs 該確認的完整判準</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.1.0</p>
]]></content:encoded></item><item><title>Cost &amp; Checkpoint — 覆寫成本告知與 revert checkpoint</title><link>https://tarrragon.github.io/blog/skills/requirement-protocol/cost-and-checkpoint/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/requirement-protocol/cost-and-checkpoint/</guid><description>&lt;p>兩個情境的協議合併：&lt;strong>對抗多層的覆寫成本告知&lt;/strong> + &lt;strong>「先還原 / 先重來」類退出指令處理&lt;/strong>。共同主軸 = 把成本攤開、讓使用者參與決策、保留可逆性。&lt;/p>
&lt;p>適用：&lt;/p>
&lt;ul>
&lt;li>客製需求要對抗多層（vendor CSS、framework reconciliation、browser default、UA stylesheet）&lt;/li>
&lt;li>收到「先還原」「先重來」「換個方向」「我們重新開始」這類指令&lt;/li>
&lt;/ul>
&lt;p>不適用：純 greenfield 開發（沒有舊代碼要對抗、沒有探索成果要保留）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋成本告知模板、checkpoint 命名慣例、reset 前的確認協議。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="何時參閱本文件">何時參閱本文件&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>訊號&lt;/th>
 &lt;th>該做的第一件事&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>客製需求看似簡單但要打到 vendor / framework / UA 多層&lt;/td>
 &lt;td>在寫第一條規則前先報成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將連寫 ≥ 3 條 &lt;code>!important&lt;/code> / 複雜 selector&lt;/td>
 &lt;td>停 — 寫成本報告、問使用者意願&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>使用者說「先還原」「先重來」「思路不對、換」&lt;/td>
 &lt;td>確認還原目標 + 是否要 commit 當前進度&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>探索了一個方向、最後沒採用&lt;/td>
 &lt;td>commit 一個 checkpoint 標「explored, not adopted」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將執行 &lt;code>git reset --hard&lt;/code> / &lt;code>git checkout .&lt;/code>&lt;/td>
 &lt;td>先確認哪些工作要保留、哪些要丟&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼成本要攤開為什麼-revert-要-checkpoint">為什麼成本要攤開、為什麼 revert 要 checkpoint&lt;/h2>
&lt;h3 id="成本攤開的價值">成本攤開的價值&lt;/h3>
&lt;p>當客製要對抗多層、執行者沉默地堆疊 &lt;code>!important&lt;/code> + specificity hack + polyfill — 結果使用者：&lt;/p>
&lt;ol>
&lt;li>看到「能用」的成果、以為成本低&lt;/li>
&lt;li>升級 vendor / 換 browser 後壞掉、驚訝於維護負擔&lt;/li>
&lt;li>不知道有沒有更便宜的替代方案（換 vendor、放棄該客製、改設計）&lt;/li>
&lt;/ol>
&lt;p>把成本攤開 = 使用者&lt;strong>在執行前&lt;/strong>就決定值不值、不在事後後悔。&lt;/p>
&lt;h3 id="revert-含-checkpoint-的價值">Revert 含 checkpoint 的價值&lt;/h3>
&lt;p>探索的成果即使沒採用、仍然是「為什麼不採用」的證據。直接清空：&lt;/p>
&lt;ol>
&lt;li>下次遇到類似需求、可能再走一遍同樣的死路&lt;/li>
&lt;li>失去 A 跟 B 兩條路的對比基礎&lt;/li>
&lt;li>部分技術選擇（命名、結構）可能仍有用、被連帶丟掉&lt;/li>
&lt;/ol>
&lt;p>Checkpoint 把「探索」與「採用」分開記錄、保留比較與恢復的可能。&lt;/p>
&lt;hr>
&lt;h2 id="成本告知協議">成本告知協議&lt;/h2>
&lt;h3 id="步驟-1列出對抗的層">步驟 1：列出對抗的層&lt;/h3>
&lt;p>寫第一條規則前、列出將打到哪幾層：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層&lt;/th>
 &lt;th>對抗代價&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Browser UA 樣式&lt;/td>
 &lt;td>低 — UA 變動慢、跨瀏覽器差異是固定問題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Vendor library&lt;/td>
 &lt;td>中 — 升級時可能變、需追蹤 vendor changelog&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Framework runtime&lt;/td>
 &lt;td>高 — reconciliation 會清掉、需在邊界外操作&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>自家舊 CSS&lt;/td>
 &lt;td>低 — 完全可控&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="步驟-2估規則數量與風險">步驟 2：估規則數量與風險&lt;/h3>





&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">這個客製需要打到：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">- Vendor CSS（pagefind 主題色）：寫 3-4 條規則覆蓋預設色
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">- Framework reconciliation（drawer 內容會被重渲染）：把客製 UI 放邊界外
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">- 升級風險：pagefind 升級 minor 版本、選擇器改名 → 客製樣式失效
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">建議方案：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">A. 完整客製（如上）— 工時 1 hr、升級時要重檢
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">B. 只改 CSS variable（如果 vendor 提供）— 工時 5 min、升級安全
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">C. 放棄客製、用 vendor 預設 — 工時 0、視覺差異使用者要接受
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">推薦 B（如果 vendor 有提供 var）、否則 A。哪一個？&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="步驟-3使用者選擇後再開始">步驟 3：使用者選擇後再開始&lt;/h3>
&lt;p>不管選 A / B / C、選擇本身已經被攤開。使用者後續看到維護負擔、不會驚訝。&lt;/p></description><content:encoded><![CDATA[<p>兩個情境的協議合併：<strong>對抗多層的覆寫成本告知</strong> + <strong>「先還原 / 先重來」類退出指令處理</strong>。共同主軸 = 把成本攤開、讓使用者參與決策、保留可逆性。</p>
<p>適用：</p>
<ul>
<li>客製需求要對抗多層（vendor CSS、framework reconciliation、browser default、UA stylesheet）</li>
<li>收到「先還原」「先重來」「換個方向」「我們重新開始」這類指令</li>
</ul>
<p>不適用：純 greenfield 開發（沒有舊代碼要對抗、沒有探索成果要保留）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋成本告知模板、checkpoint 命名慣例、reset 前的確認協議。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>客製需求看似簡單但要打到 vendor / framework / UA 多層</td>
          <td>在寫第一條規則前先報成本</td>
      </tr>
      <tr>
          <td>即將連寫 ≥ 3 條 <code>!important</code> / 複雜 selector</td>
          <td>停 — 寫成本報告、問使用者意願</td>
      </tr>
      <tr>
          <td>使用者說「先還原」「先重來」「思路不對、換」</td>
          <td>確認還原目標 + 是否要 commit 當前進度</td>
      </tr>
      <tr>
          <td>探索了一個方向、最後沒採用</td>
          <td>commit 一個 checkpoint 標「explored, not adopted」</td>
      </tr>
      <tr>
          <td>即將執行 <code>git reset --hard</code> / <code>git checkout .</code></td>
          <td>先確認哪些工作要保留、哪些要丟</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼成本要攤開為什麼-revert-要-checkpoint">為什麼成本要攤開、為什麼 revert 要 checkpoint</h2>
<h3 id="成本攤開的價值">成本攤開的價值</h3>
<p>當客製要對抗多層、執行者沉默地堆疊 <code>!important</code> + specificity hack + polyfill — 結果使用者：</p>
<ol>
<li>看到「能用」的成果、以為成本低</li>
<li>升級 vendor / 換 browser 後壞掉、驚訝於維護負擔</li>
<li>不知道有沒有更便宜的替代方案（換 vendor、放棄該客製、改設計）</li>
</ol>
<p>把成本攤開 = 使用者<strong>在執行前</strong>就決定值不值、不在事後後悔。</p>
<h3 id="revert-含-checkpoint-的價值">Revert 含 checkpoint 的價值</h3>
<p>探索的成果即使沒採用、仍然是「為什麼不採用」的證據。直接清空：</p>
<ol>
<li>下次遇到類似需求、可能再走一遍同樣的死路</li>
<li>失去 A 跟 B 兩條路的對比基礎</li>
<li>部分技術選擇（命名、結構）可能仍有用、被連帶丟掉</li>
</ol>
<p>Checkpoint 把「探索」與「採用」分開記錄、保留比較與恢復的可能。</p>
<hr>
<h2 id="成本告知協議">成本告知協議</h2>
<h3 id="步驟-1列出對抗的層">步驟 1：列出對抗的層</h3>
<p>寫第一條規則前、列出將打到哪幾層：</p>
<table>
  <thead>
      <tr>
          <th>層</th>
          <th>對抗代價</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Browser UA 樣式</td>
          <td>低 — UA 變動慢、跨瀏覽器差異是固定問題</td>
      </tr>
      <tr>
          <td>Vendor library</td>
          <td>中 — 升級時可能變、需追蹤 vendor changelog</td>
      </tr>
      <tr>
          <td>Framework runtime</td>
          <td>高 — reconciliation 會清掉、需在邊界外操作</td>
      </tr>
      <tr>
          <td>自家舊 CSS</td>
          <td>低 — 完全可控</td>
      </tr>
  </tbody>
</table>
<h3 id="步驟-2估規則數量與風險">步驟 2：估規則數量與風險</h3>





<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">這個客製需要打到：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- Vendor CSS（pagefind 主題色）：寫 3-4 條規則覆蓋預設色
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- Framework reconciliation（drawer 內容會被重渲染）：把客製 UI 放邊界外
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 升級風險：pagefind 升級 minor 版本、選擇器改名 → 客製樣式失效
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">建議方案：
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">A. 完整客製（如上）— 工時 1 hr、升級時要重檢
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">B. 只改 CSS variable（如果 vendor 提供）— 工時 5 min、升級安全
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">C. 放棄客製、用 vendor 預設 — 工時 0、視覺差異使用者要接受
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">推薦 B（如果 vendor 有提供 var）、否則 A。哪一個？</span></span></code></pre></div><h3 id="步驟-3使用者選擇後再開始">步驟 3：使用者選擇後再開始</h3>
<p>不管選 A / B / C、選擇本身已經被攤開。使用者後續看到維護負擔、不會驚訝。</p>
<hr>
<h2 id="revert--checkpoint-協議">Revert / Checkpoint 協議</h2>
<h3 id="步驟-1確認還原目標">步驟 1：確認還原目標</h3>
<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">「還原」是指：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">(a) 丟掉所有未 commit 的修改、回到 HEAD
</span></span><span class="line"><span class="ln">4</span><span class="cl">(b) 回到某個特定 commit（哪一個？）
</span></span><span class="line"><span class="ln">5</span><span class="cl">(c) 部分還原（哪些檔案 / 哪些功能）
</span></span><span class="line"><span class="ln">6</span><span class="cl">(d) 換思路、但保留結構（命名、檔案組織保留、實作換掉）
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">我建議先做 commit 把當前進度保存、再 reset — 您是哪一種？</span></span></code></pre></div><h3 id="步驟-2commit-當前進度當-checkpoint">步驟 2：commit 當前進度當 checkpoint</h3>
<p>不管是哪種還原、先 commit：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">git add -A
</span></span><span class="line"><span class="ln">2</span><span class="cl">git commit -m <span class="s2">&#34;checkpoint: explored [方向 X], not adopted
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="s2">- 嘗試了 [做法 A]、結果 [現象]
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="s2">- 假設 [Z] 驗證後不成立
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="s2">- 保留供未來對比、不採用為最終解
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="s2">&#34;</span></span></span></code></pre></div><p>Checkpoint commit 的特徵：</p>
<ul>
<li>主題明確含「checkpoint」「explored」「not adopted」字樣</li>
<li>body 寫「為什麼不採用」、不只寫「做了什麼」</li>
<li>在後續 main branch 上不會被 merge 進去（用 branch 隔離或日後 rebase 丟）</li>
</ul>
<h3 id="步驟-3執行-reset">步驟 3：執行 reset</h3>
<p>確認 checkpoint commit 完成後、執行使用者要的還原：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 視類型而定</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">git reset --hard &lt;target&gt;      <span class="c1"># 完全還原</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">git checkout &lt;commit&gt; -- &lt;file&gt; <span class="c1"># 部分還原</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">git revert &lt;commit&gt;             <span class="c1"># 保留歷史的還原</span></span></span></code></pre></div><p>危險動作（<code>git reset --hard</code>、<code>git checkout .</code>、<code>git clean -fd</code>）執行前再次跟使用者確認 — 即使有 checkpoint commit、也要避免把 checkpoint 自己 reset 掉。</p>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1覆寫成本">範例 1：覆寫成本</h3>
<blockquote>
<p>使用者：「pagefind 搜尋框的顏色改成我們的品牌色」</p></blockquote>
<p><strong>錯</strong>（直接做、沒攤成本）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">.</span><span class="nc">pagefind-ui__form</span> <span class="p">{</span> <span class="k">background</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">brand</span><span class="p">)</span> <span class="cp">!important</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">.</span><span class="nc">pagefind-ui__search-input</span> <span class="p">{</span> <span class="k">background</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">brand</span><span class="p">)</span> <span class="cp">!important</span><span class="p">;</span> <span class="k">color</span><span class="p">:</span> <span class="kc">white</span> <span class="cp">!important</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">.</span><span class="nc">pagefind-ui__button</span> <span class="p">{</span> <span class="k">background</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">brand</span><span class="o">-</span><span class="n">dark</span><span class="p">)</span> <span class="cp">!important</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c">/* ... 8 條 !important 後、終於看起來對 */</span></span></span></code></pre></div><p>兩週後 pagefind 升級、class 名稱改了一個 — 全壞。使用者驚訝。</p>
<p><strong>對</strong>（先報成本）：</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">顏色客製有兩條路：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">A. Pagefind 提供的 CSS variables（看了 docs：--pagefind-ui-primary、--pagefind-ui-text 等）
</span></span><span class="line"><span class="ln">4</span><span class="cl">   → 改 4 個 var、升級安全、5 min
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">B. 直接覆寫 class 規則（要 8 條 !important）
</span></span><span class="line"><span class="ln">7</span><span class="cl">   → 升級可能壞、需要追蹤 vendor changelog、1 hr
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl">推薦 A、除非您要的視覺超出 var 能控制的範圍。哪個？</span></span></code></pre></div><h3 id="範例-2先還原處理">範例 2：「先還原」處理</h3>
<blockquote>
<p>使用者：「等等先還原、這個方向不對」</p></blockquote>
<p><strong>錯</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">git checkout .  <span class="c1"># 直接清空、丟掉 30 分鐘探索成果</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 之後想對比「為什麼方向 A 不行」時、什麼都沒留下</span></span></span></code></pre></div><p><strong>對</strong>：</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">我把當前進度先 commit 成 checkpoint、再還原 — 還原的目標是：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">(a) 回到開始這個方向之前的 commit（HEAD~3）
</span></span><span class="line"><span class="ln">4</span><span class="cl">(b) 回到 main 最新
</span></span><span class="line"><span class="ln">5</span><span class="cl">(c) 換個 branch、保留這個 branch 供以後參考
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">通常 (c) 最安全、(a) 最常見。您要哪個？</span></span></code></pre></div><p>確認後：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">git add -A
</span></span><span class="line"><span class="ln">2</span><span class="cl">git commit -m <span class="s2">&#34;checkpoint: explored grid-row layout, not adopted (drawer is form&#39;s child, grid invalid)&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">git reset --hard HEAD~4  <span class="c1"># 或使用者指定的 target</span></span></span></code></pre></div><hr>
<h2 id="checkpoint-commit-的命名慣例">Checkpoint commit 的命名慣例</h2>
<table>
  <thead>
      <tr>
          <th>前綴</th>
          <th>用途</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>checkpoint:</code></td>
          <td>探索成果、未採用、保留參考</td>
          <td><code>checkpoint: explored A approach, not adopted</code></td>
      </tr>
      <tr>
          <td><code>wip:</code></td>
          <td>進行中、之後會 rebase / squash</td>
          <td><code>wip: trying scope toggle with regex</code></td>
      </tr>
      <tr>
          <td><code>spike:</code></td>
          <td>純探索、無意採用、純驗證可行性</td>
          <td><code>spike: pagefind perf with 5000 docs</code></td>
      </tr>
  </tbody>
</table>
<p><code>checkpoint:</code> 是本文件主推 — 比 <code>wip:</code> 多了「不採用」的明確標記、未來 grep <code>git log --grep=checkpoint</code> 能快速找到「曾經試過但放棄的方向」。</p>
<hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>收到客製需求或 revert 指令時：</p>
<ul>
<li><input disabled="" type="checkbox"> 寫第一條覆寫規則前、有沒有列出「對抗哪幾層、規則數量、升級風險」？</li>
<li><input disabled="" type="checkbox"> 有沒有給使用者 ≥ 2 個選項（含「不做」或「降級客製」）？</li>
<li><input disabled="" type="checkbox"> revert 前有沒有確認還原目標的精確意圖？</li>
<li><input disabled="" type="checkbox"> revert 前有沒有 commit 一個 checkpoint？</li>
<li><input disabled="" type="checkbox"> checkpoint 的 commit message 有沒有寫「為什麼不採用」、不只寫「做了什麼」？</li>
</ul>
<p>成本沒攤、checkpoint 沒 commit → 退回去補。</p>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code>）：</p>
<ul>
<li><a href="/blog/report/override-depth-cost-report/" data-link-title="覆寫深度的成本告知" data-link-desc="客製可能對抗 UA &#43; 跨瀏覽器 &#43; framework 三層時、先報需要寫多少規則 / 哪幾條 / 殘留風險、讓使用者判斷值不值再開工。本文展開覆寫深度的事前告知 protocol。">override-depth-cost-report</a> — 覆寫深度的成本告知</li>
<li><a href="/blog/report/revert-instruction-handling/" data-link-title="「先還原」「先重來」類退出指令的處理" data-link-desc="聽到「還原 / 重來」時、先問「還原到哪個 commit？要不要先 commit 一個 checkpoint 再動、方便日後比對？」本文展開退出指令的安全處理 protocol。">revert-instruction-handling</a> — 「先還原」「先重來」類退出指令的處理</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.1.0</p>
]]></content:encoded></item><item><title>Decision Dialogue — 決策對話的五維度協議</title><link>https://tarrragon.github.io/blog/skills/requirement-protocol/decision-dialogue/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/requirement-protocol/decision-dialogue/</guid><description>&lt;p>對應觸發情境：&lt;strong>準備呈現決策給使用者選擇時&lt;/strong>（任何「該怎麼做、A 還是 B、要不要做 X」的場景）。&lt;/p>
&lt;p>本 reference 自包含、不需讀其他 reference。把 &lt;a href="https://tarrragon.github.io/blog/report/" data-link-title="Report — 開發過程的事後檢討" data-link-desc="blog 開發過程中、把實際遇到的版型 / 整合 / 框架共處等情境、整理成『應該怎麼做、沒這樣做會有什麼麻煩』的事後檢討。每篇皆為正向指引、幫助下一輪同類任務跳過反覆試錯。">#74-#79&lt;/a> 系列翻譯成可直接套用的協議步驟與模板。&lt;/p>
&lt;hr>
&lt;h2 id="核心命題">核心命題&lt;/h2>
&lt;p>對話中要使用者決策時、有五個獨立維度可以選擇 — &lt;strong>不該預設 collapse 到單一格子&lt;/strong>：&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>呈現格式&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;/tr>
 &lt;tr>
 &lt;td>批次邊界&lt;/td>
 &lt;td>一次做完&lt;/td>
 &lt;td>分批 ship&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>時間軸&lt;/td>
 &lt;td>立刻決&lt;/td>
 &lt;td>結構性延後&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>選項類型&lt;/td>
 &lt;td>單選 radio&lt;/td>
 &lt;td>複選 checkbox&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>預設都選窄格 = 用最少自由度的問題塞使用者。每個維度該選哪邊、依情境 reason about、不是無腦套預設。&lt;/p>
&lt;hr>
&lt;h2 id="五步判讀依序檢查">五步判讀（依序檢查）&lt;/h2>
&lt;h3 id="步驟-1選項類型--是執行還是反省">步驟 1：選項類型 — 是執行還是反省？&lt;/h3>
&lt;p>&lt;strong>判準&lt;/strong>：「這次 output 該收斂到一個答案、還是攤開多面向？」&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>情境&lt;/th>
 &lt;th>預設&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>執行類（用 A 還是 B 工具、選哪個策略）&lt;/td>
 &lt;td>單選 + 推薦&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>反省類（這次學到什麼、下一步該往哪走）&lt;/td>
 &lt;td>複選、明示「互不衝突可全選」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>反例&lt;/strong>：把「我們可以做卡片 / 測試 / 改流程 / 寫文章哪個？」當單選 — 強迫使用者排序、結果只有第一個被做。&lt;/p>
&lt;p>&lt;strong>修法&lt;/strong>：列全 + 標「都該做、優先 1+2、3-4 下輪」、把「全做」「跳過某幾個」「調順序」三種回應全列為合法。&lt;/p>
&lt;h3 id="步驟-2時間軸--現在能決嗎">步驟 2：時間軸 — 現在能決嗎？&lt;/h3>
&lt;p>&lt;strong>判準&lt;/strong>：「我（agent）有沒有提供能讓使用者下決定的全部資訊？」&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>情境&lt;/th>
 &lt;th>預設&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Context 完整、選項都展開&lt;/td>
 &lt;td>立刻決&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Context 缺（依賴未跑的測試 / 未讀的 code / 未完成的觀測）&lt;/td>
 &lt;td>&lt;strong>延後 + 寫條件&lt;/strong>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>反例&lt;/strong>：使用者說「我再想想」、agent 加壓「那你決定了嗎？」— 把延後當失敗。&lt;/p>
&lt;p>&lt;strong>修法&lt;/strong>：決策表加最後一欄「&lt;strong>延後（補 X 再決）&lt;/strong>」、寫具體條件（補完 X / 等到 Y / 跑完 Z 觀測）— 延後不是逃避、是有 next step 的另一種決策。&lt;/p>
&lt;h3 id="步驟-3策略數--單選還是疊加">步驟 3：策略數 — 單選還是疊加？&lt;/h3>
&lt;p>&lt;strong>判準&lt;/strong>：策略間 (1) 解不同層、(2) 沒副作用衝突、(3) 增量成本可接受 → 三條全滿足 = 該疊加。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>組合&lt;/th>
 &lt;th>範例&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Structural + UX&lt;/td>
 &lt;td>Multi-index（解根因）+ Honest progress UI（解感知）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Defensive + Optimistic&lt;/td>
 &lt;td>輸入驗證 + 預設值 / 自動修正&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Now + Later&lt;/td>
 &lt;td>先 ship X 解眼前、Y 下輪做（時間軸疊加）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>反例&lt;/strong>：「五策略選一」當預設、推薦時只列一個策略。&lt;/p>
&lt;p>&lt;strong>修法&lt;/strong>：呈現選項時主動標「也可以加 X」「先 D 後 B/C」、把疊加組合列為合法回應。&lt;/p>
&lt;h3 id="步驟-4批次邊界--一次還是分批">步驟 4：批次邊界 — 一次還是分批？&lt;/h3>
&lt;p>&lt;strong>三軸切分&lt;/strong>：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>軸&lt;/th>
 &lt;th>低（先 ship）&lt;/th>
 &lt;th>高（下輪）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>使用者可見性&lt;/td>
 &lt;td>UI 改變、訊息精準&lt;/td>
 &lt;td>純內部結構&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>風險暴露面&lt;/td>
 &lt;td>純加法、不影響既有 path&lt;/td>
 &lt;td>替換、刪除、結構重組&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>驗證需求&lt;/td>
 &lt;td>unit test 可驗&lt;/td>
 &lt;td>需長時觀測、A/B&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>先 ship 甜蜜點&lt;/strong>：高可見 + 低風險 + 低驗證 — 例：UX hint、empty state 訊息、明顯 UI 修正。&lt;/p></description><content:encoded><![CDATA[<p>對應觸發情境：<strong>準備呈現決策給使用者選擇時</strong>（任何「該怎麼做、A 還是 B、要不要做 X」的場景）。</p>
<p>本 reference 自包含、不需讀其他 reference。把 <a href="/blog/report/" data-link-title="Report — 開發過程的事後檢討" data-link-desc="blog 開發過程中、把實際遇到的版型 / 整合 / 框架共處等情境、整理成『應該怎麼做、沒這樣做會有什麼麻煩』的事後檢討。每篇皆為正向指引、幫助下一輪同類任務跳過反覆試錯。">#74-#79</a> 系列翻譯成可直接套用的協議步驟與模板。</p>
<hr>
<h2 id="核心命題">核心命題</h2>
<p>對話中要使用者決策時、有五個獨立維度可以選擇 — <strong>不該預設 collapse 到單一格子</strong>：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>預設窄格</th>
          <th>鬆綁後</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>呈現格式</td>
          <td>開放問</td>
          <td>選項表 + 推薦</td>
      </tr>
      <tr>
          <td>策略數</td>
          <td>單選</td>
          <td>主 + 補強疊加</td>
      </tr>
      <tr>
          <td>批次邊界</td>
          <td>一次做完</td>
          <td>分批 ship</td>
      </tr>
      <tr>
          <td>時間軸</td>
          <td>立刻決</td>
          <td>結構性延後</td>
      </tr>
      <tr>
          <td>選項類型</td>
          <td>單選 radio</td>
          <td>複選 checkbox</td>
      </tr>
  </tbody>
</table>
<p>預設都選窄格 = 用最少自由度的問題塞使用者。每個維度該選哪邊、依情境 reason about、不是無腦套預設。</p>
<hr>
<h2 id="五步判讀依序檢查">五步判讀（依序檢查）</h2>
<h3 id="步驟-1選項類型--是執行還是反省">步驟 1：選項類型 — 是執行還是反省？</h3>
<p><strong>判準</strong>：「這次 output 該收斂到一個答案、還是攤開多面向？」</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>預設</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>執行類（用 A 還是 B 工具、選哪個策略）</td>
          <td>單選 + 推薦</td>
      </tr>
      <tr>
          <td>反省類（這次學到什麼、下一步該往哪走）</td>
          <td>複選、明示「互不衝突可全選」</td>
      </tr>
  </tbody>
</table>
<p><strong>反例</strong>：把「我們可以做卡片 / 測試 / 改流程 / 寫文章哪個？」當單選 — 強迫使用者排序、結果只有第一個被做。</p>
<p><strong>修法</strong>：列全 + 標「都該做、優先 1+2、3-4 下輪」、把「全做」「跳過某幾個」「調順序」三種回應全列為合法。</p>
<h3 id="步驟-2時間軸--現在能決嗎">步驟 2：時間軸 — 現在能決嗎？</h3>
<p><strong>判準</strong>：「我（agent）有沒有提供能讓使用者下決定的全部資訊？」</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>預設</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Context 完整、選項都展開</td>
          <td>立刻決</td>
      </tr>
      <tr>
          <td>Context 缺（依賴未跑的測試 / 未讀的 code / 未完成的觀測）</td>
          <td><strong>延後 + 寫條件</strong></td>
      </tr>
  </tbody>
</table>
<p><strong>反例</strong>：使用者說「我再想想」、agent 加壓「那你決定了嗎？」— 把延後當失敗。</p>
<p><strong>修法</strong>：決策表加最後一欄「<strong>延後（補 X 再決）</strong>」、寫具體條件（補完 X / 等到 Y / 跑完 Z 觀測）— 延後不是逃避、是有 next step 的另一種決策。</p>
<h3 id="步驟-3策略數--單選還是疊加">步驟 3：策略數 — 單選還是疊加？</h3>
<p><strong>判準</strong>：策略間 (1) 解不同層、(2) 沒副作用衝突、(3) 增量成本可接受 → 三條全滿足 = 該疊加。</p>
<table>
  <thead>
      <tr>
          <th>組合</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Structural + UX</td>
          <td>Multi-index（解根因）+ Honest progress UI（解感知）</td>
      </tr>
      <tr>
          <td>Defensive + Optimistic</td>
          <td>輸入驗證 + 預設值 / 自動修正</td>
      </tr>
      <tr>
          <td>Now + Later</td>
          <td>先 ship X 解眼前、Y 下輪做（時間軸疊加）</td>
      </tr>
  </tbody>
</table>
<p><strong>反例</strong>：「五策略選一」當預設、推薦時只列一個策略。</p>
<p><strong>修法</strong>：呈現選項時主動標「也可以加 X」「先 D 後 B/C」、把疊加組合列為合法回應。</p>
<h3 id="步驟-4批次邊界--一次還是分批">步驟 4：批次邊界 — 一次還是分批？</h3>
<p><strong>三軸切分</strong>：</p>
<table>
  <thead>
      <tr>
          <th>軸</th>
          <th>低（先 ship）</th>
          <th>高（下輪）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>使用者可見性</td>
          <td>UI 改變、訊息精準</td>
          <td>純內部結構</td>
      </tr>
      <tr>
          <td>風險暴露面</td>
          <td>純加法、不影響既有 path</td>
          <td>替換、刪除、結構重組</td>
      </tr>
      <tr>
          <td>驗證需求</td>
          <td>unit test 可驗</td>
          <td>需長時觀測、A/B</td>
      </tr>
  </tbody>
</table>
<p><strong>先 ship 甜蜜點</strong>：高可見 + 低風險 + 低驗證 — 例：UX hint、empty state 訊息、明顯 UI 修正。</p>
<p><strong>反例</strong>：「等所有結構性修法都做完才 ship」— 把重要程度誤當成 ship 順序。</p>
<p><strong>修法</strong>：明示「ship 順序 ≠ 重要程度」、可見性高 + 風險低的部分先 ship。</p>
<h3 id="步驟-5呈現格式--開放還是結構">步驟 5：呈現格式 — 開放還是結構？</h3>
<p><strong>判準</strong>：「我能不能列選項 + 適配性 + 推薦？」</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>格式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>有客觀適配性可比</td>
          <td>結構表 + 推薦 + 開放修改</td>
      </tr>
      <tr>
          <td>純探索 / 主觀偏好 / 命名</td>
          <td>開放問</td>
      </tr>
  </tbody>
</table>
<p><strong>反例</strong>：「你想怎麼做？」— 把整個問題空間丟回去。</p>
<p><strong>修法</strong>：</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">## 選項
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">| 選項 | 適配性 | 取捨 |
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">|---|---|---|
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">| A ⋯⋯ | ⋯⋯ | ⋯⋯ |
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">| B ⋯⋯ | ⋯⋯ | ⋯⋯ |
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">| 延後（補 X 再決） | 等 Y | 條件：⋯⋯ |
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">## 推薦
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">我推薦 **A**、因為 ⋯⋯。想改成 B 或補充延後條件、跟我說。
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">## 你的選擇空間
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">- 同意（A）
</span></span><span class="line"><span class="ln">16</span><span class="cl">- 改（B、原因 ⋯⋯）
</span></span><span class="line"><span class="ln">17</span><span class="cl">- 加 / 減 / 疊加組合
</span></span><span class="line"><span class="ln">18</span><span class="cl">- 延後
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 任意組合可複選（除非互斥）</span></span></code></pre></div><hr>
<h2 id="完整套用範本">完整套用範本</h2>
<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">## 我看到的選項
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">| 選項 | 適配性 | 取捨 |
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">|---|---|---|
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">| A 結構性修法 | 解根因 | 風險高、要驗證 |
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">| B UX 補強 | 立即可見 | 不解根因 |
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">| C 不做 | 0 成本 | 使用者繼續手動 |
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">| **延後（補 telemetry 再決）** | 等 context | 條件：跑完 1 週觀測 |
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">## 推薦組合
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">**B 先 ship、A 下輪**（疊加 + 分批）— B 解眼前痛、A 在 telemetry 證實後再投入結構修法。C 不選因為使用者會抱怨。
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">## 你的選擇空間
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">- 同意（B 現在、A 下輪）
</span></span><span class="line"><span class="ln">17</span><span class="cl">- 改順序（A 先、B 下輪）
</span></span><span class="line"><span class="ln">18</span><span class="cl">- 加 / 減：把 C 加進來、或拿掉 B
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 延後：先補 telemetry 再決
</span></span><span class="line"><span class="ln">20</span><span class="cl">- **任意組合可複選**（除非說明互斥）</span></span></code></pre></div><hr>
<h2 id="self-check-checklist">Self-check checklist</h2>
<p>呈現決策前用以下 checklist 檢查（每條問自己「這維度該選窄格還是鬆綁？」）：</p>
<ul>
<li><input disabled="" type="checkbox"> <strong>選項類型</strong>：這是執行（單選）還是反省（複選）題？反省題我有沒有明示「互不衝突」？</li>
<li><input disabled="" type="checkbox"> <strong>時間軸</strong>：context 夠嗎？不夠的話我有沒有列「延後（補 X 再決）」選項？</li>
<li><input disabled="" type="checkbox"> <strong>策略數</strong>：選項間能不能疊加？三條判準（不同層 / 無衝突 / 成本可接受）滿足的話有沒有提組合？</li>
<li><input disabled="" type="checkbox"> <strong>批次邊界</strong>：先 ship 哪部分？有沒有把「先 X 後 Y」明示為合法回應？</li>
<li><input disabled="" type="checkbox"> <strong>呈現格式</strong>：用了結構表 + 推薦嗎？還是丟一句「你想怎麼做」？</li>
</ul>
<p>任一條沒做、退一步補上、再呈現決策。</p>
<hr>
<h2 id="反模式快速辨識">反模式快速辨識</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>五維 collapse 到</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>&ldquo;你想怎麼做？&rdquo;</td>
          <td>開放問 + 立刻 + 單選 + 一次 + 單策略</td>
      </tr>
      <tr>
          <td>&ldquo;推薦 A、要嗎？&rdquo;</td>
          <td>結構但只 1 選 + 立刻 + 單選 + 一次 + 單策略</td>
      </tr>
      <tr>
          <td>&ldquo;ABCDE 你選哪個？&rdquo;</td>
          <td>結構 + 立刻 + 單選 radio + 一次 + 單策略</td>
      </tr>
      <tr>
          <td>&ldquo;做完 X 才能繼續&rdquo;</td>
          <td>結構 + 立刻 + 單選 + 一次 + 單策略（漏分批）</td>
      </tr>
      <tr>
          <td>&ldquo;這次學到 X、下次注意&rdquo;</td>
          <td>反省題壓單選、立刻、一次</td>
      </tr>
  </tbody>
</table>
<p>每個變種都是「五個維度都選窄格」的展現 — 看到任一個出現在自己 draft、立刻退回五步判讀。</p>
<p>特別注意 <strong>Yes/No 二選</strong>（<a href="/blog/report/yes-no-binary-collapse/" data-link-title="Yes/No 二選是隱式 collapse：把多選空間壓成 1 bit" data-link-desc="「需要我繼續嗎？」「要做嗎？」「OK 嗎？」這類 yes/no 問句、看似最簡單其實是五維 collapse 的極致形態：1 bit 編碼掉「改 / 延後 / 疊加 / 分批 / 反問」五種合法回應。本卡是 #74-#79 系列在「最常見、最隱形」變種的特化卡。">#80</a>）— 「需要 X 嗎？」「OK 嗎？」「要繼續嗎？」這類最常見、最隱形的 collapse、把多選空間壓成 1 bit。修法是把 yes/no 翻成「現在做 X / 改 Y / 延後到 Z / 疊加 X+Y」的多選表。</p>
<hr>
<h2 id="真實-dogfood-例子從本-skill-設計過程蒐集">真實 dogfood 例子（從本 skill 設計過程蒐集）</h2>
<p>寫這份 reference 的對話本身、agent 多次出現 collapse 反模式。記下作為將來 self-check 的具體素材：</p>
<h3 id="例-1commit-後的下一步變-yesno">例 1：commit 後的「下一步」變 yes/no</h3>
<p><strong>Bad</strong>：「下一步依你之前的決策是 ship D（UX hint），需要我繼續嗎？」</p>
<p><strong>collapse</strong>：呈現格式（單一推薦無選項）+ 選項類型（yes/no）+ 策略（單一 D）+ 批次（無選項）+ 時間（隱含立刻）= 五維全 collapse + binary。</p>
<p><strong>Good</strong>：列「立刻 ship D / 寫 case study / 反省迭代 / 延後」四選 + 推薦 + 「可複選」。</p>
<h3 id="例-2列候選用無適配欄的-bullet">例 2：列候選用無適配欄的 bullet</h3>
<p><strong>Bad</strong>：</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">下一層迭代候選（多選）：
</span></span><span class="line"><span class="ln">2</span><span class="cl">1. dogfood 檢驗
</span></span><span class="line"><span class="ln">3</span><span class="cl">2. L3 trigger
</span></span><span class="line"><span class="ln">4</span><span class="cl">3. case study
</span></span><span class="line"><span class="ln">5</span><span class="cl">4. 倒過來補卡
</span></span><span class="line"><span class="ln">6</span><span class="cl">5. 檢驗 #75 對 #46-#50</span></span></code></pre></div><p><strong>collapse</strong>：呈現格式（沒適配性欄）+ 沒明示「互不衝突可全做」+ 沒推薦組合 + 沒延後選項。</p>
<p><strong>Good</strong>：把選項排成「| 選項 | 適配性 | 取捨 |」表 + 標「都不衝突可全做」+ 推薦「先 1+3、4-5 下輪」+ 加「延後（明天再決）」欄。</p>
<h3 id="例-3執行類決策列點未含推薦">例 3：執行類決策列點未含推薦</h3>
<p><strong>Bad</strong>：列出 ABC 三選但騎牆「我推薦 A、不過 B 也行」。</p>
<p><strong>collapse</strong>：呈現格式 layer 3「推薦不夠明確」變種、把選擇權騎牆推回給使用者。</p>
<p><strong>Good</strong>：「我推薦 <strong>A</strong>、因為 X。改成 B 的條件是 Y（如果 Y 成立、改 B）。想直接改告訴我。」</p>
<h3 id="例-4反省題壓單選">例 4：反省題壓單選</h3>
<p><strong>Bad</strong>：「這次最該做的反省是 1（補卡片）— 要做嗎？」</p>
<p><strong>collapse</strong>：反省題用 radio + 推薦 + yes/no。漏掉「全做」「跳過 1 做 2」「延後反省」三種合法回應。</p>
<p><strong>Good</strong>：列五個反省方向、明示「互不衝突」、推薦優先順序、「全做」「跳過某幾個」「調順序」全列為合法。</p>
<p>每個 Bad 例都是 <a href="/blog/report/decision-dialogue-dimensions/" data-link-title="決策對話的五個維度：保持完整選擇空間" data-link-desc="對話中的「決策」不是單一動作、是多維度選擇空間：呈現格式 / 策略疊加 / 批次邊界 / 時間軸 / 選項類型。預設多半 collapse 到最窄格（開放問 &#43; 單策略 &#43; 一次完成 &#43; 立刻決 &#43; 單選）、塞使用者進最少自由度的盒子。本卡是 #74-#78 的上層串連 — 五張卡各對應一個維度的鬆綁。">#79 五維度</a> collapse 的具體實例 — 寫的當下覺得「夠精簡」、實際藏掉 N 個合法選項。將來看到自己寫類似格式、立刻退回多選展開。</p>
<hr>
<h2 id="對應抽象層原則">對應抽象層原則</h2>
<ul>
<li><a href="/blog/report/decision-presentation-options-recommendation/" data-link-title="決策呈現：選項 &#43; 推薦 &#43; 開放修改" data-link-desc="讓使用者做決定時、不要開放問「你覺得呢」 — 給選項、加適配性、標推薦、開放修改。開放問把「整理問題」的成本丟給使用者、推薦把判斷攤開供質疑、開放修改保留使用者的最終決定權。本卡是 #58 篩選三問、requirement-protocol 原則 1 的呈現面展開。">#74 決策呈現格式</a> — 步驟 5 的詳細展開</li>
<li><a href="/blog/report/main-strategy-plus-supplementary/" data-link-title="主策略 &#43; 補強策略：選擇不必互斥" data-link-desc="多策略並非「五選一」、可分層疊加：root-cause fix（解結構問題） &#43; UX 補強（解使用者感知）通常雙打比單選更穩。判準三條：解不同層 / 沒副作用衝突 / 增量成本可接受。把「策略選擇」預設成單選、會放掉互補可能、產生「結構修了但使用者體驗仍差」或「UX 蓋過去但結構還壞」。">#75 主策略 + 補強疊加</a> — 步驟 3 的詳細展開</li>
<li><a href="/blog/report/incremental-shipping-criteria/" data-link-title="分批 ship：低風險可見價值先行、結構性下輪" data-link-desc="「一次 ship 全部」的衝動 vs 「分批 ship」的設計：判準三軸（使用者可見性 / 風險暴露面 / 驗證需求）。低風險 &#43; 高可見 = 立刻 ship；高風險 &#43; 需驗證 = 下輪。對抗「完整才完整」的全做衝動、避免一次塞太多 review surface 拖延上線。">#76 分批 ship 準則</a> — 步驟 4 的詳細展開</li>
<li><a href="/blog/report/decide-later-as-valid-option/" data-link-title="「現在不決定」是合法選項：context 不足時延後決策" data-link-desc="被問到時不一定要立刻答 — 「先補 context、回頭再決」是合法選項、卻常被當「拖延」忽略。LLM / agent 預設「問了就要立刻答」是錯誤前提：使用者有權延後到 context 補齊、推薦時應主動標出「也可選『先 X 再回來決』」。本卡是 #58 篩選三問、#74 決策呈現的時間軸延伸。">#77 「現在不決定」是合法選項</a> — 步驟 2 的詳細展開</li>
<li><a href="/blog/report/retrospective-multi-select-default/" data-link-title="反省任務預設複選：互斥要證明、不互斥是預設" data-link-desc="反省 / retrospective / 改進方向類問題、預設應給「複選」而非「單選」 — 互斥需要明示證明、不互斥是預設。用 radio 限縮會讓使用者被迫排序、丟失多面向的同時性。本卡是 #74 決策呈現的反省場景特化、跟一般「執行類決策」（多半互斥）對立。">#78 反省任務預設複選</a> — 步驟 1 的詳細展開</li>
<li><a href="/blog/report/decision-dialogue-dimensions/" data-link-title="決策對話的五個維度：保持完整選擇空間" data-link-desc="對話中的「決策」不是單一動作、是多維度選擇空間：呈現格式 / 策略疊加 / 批次邊界 / 時間軸 / 選項類型。預設多半 collapse 到最窄格（開放問 &#43; 單策略 &#43; 一次完成 &#43; 立刻決 &#43; 單選）、塞使用者進最少自由度的盒子。本卡是 #74-#78 的上層串連 — 五張卡各對應一個維度的鬆綁。">#79 決策對話的五維度</a> — 上層 meta-原則</li>
<li><a href="/blog/report/ease-of-writing-vs-intent-alignment/" data-link-title="寫作便利度跟意圖對齊反相關" data-link-desc="寫程式時最容易寫出的版本、通常是離意圖最遠的版本。便利度建立在「現有上下文 / 已 materialize 資料 / 已存在 API」上、而意圖對齊需要找到正確的層、處理上游、跨抽象層 — 兩者方向相反。識別這個反相關 = 識別自己掉進「容易寫的陷阱」。">#67 寫作便利度跟意圖對齊反相關</a> — 為什麼窄格是預設（容易寫）</li>
<li><a href="/blog/report/external-trigger-for-high-roi-work/" data-link-title="高 ROI 無外部觸發的工作會被結構性跳過" data-link-desc="工作有兩個獨立維度：ROI 高低 &#43; 是否有外部觸發。高 ROI &#43; 無觸發 = ROI 的承諾、拖延的現實。靠紀律不可行 — 結構性偏差需要結構性對策（外部觸發 / CI / hook / 排程 / pair）。本卡是 #67 便利反相關、#68 checkpoint 跳過、#69 RED 跳過的共同上位原則。">#72 高 ROI 無觸發</a> — 為什麼鬆綁需要協議結構（不靠紀律）</li>
<li><a href="/blog/report/yes-no-binary-collapse/" data-link-title="Yes/No 二選是隱式 collapse：把多選空間壓成 1 bit" data-link-desc="「需要我繼續嗎？」「要做嗎？」「OK 嗎？」這類 yes/no 問句、看似最簡單其實是五維 collapse 的極致形態：1 bit 編碼掉「改 / 延後 / 疊加 / 分批 / 反問」五種合法回應。本卡是 #74-#79 系列在「最常見、最隱形」變種的特化卡。">#80 Yes/No 二選是隱式 collapse</a> — 五維 collapse 的極致形態、最常見最隱形</li>
<li><a href="/blog/report/cards-as-living-system-iteration/" data-link-title="卡片系統的迭代浮現：原子卡 → meta-卡 → reference 三層展開" data-link-desc="知識卡片系統不是一次寫成、是 dialogue → 原子卡 → meta-卡 → reference 的迭代浮現。每一輪迭代解決上一輪的 over-fit / under-fit、串連分散的卡片、抽出 meta-原則、最後沉澱成可直接套用的 reference 文件。本卡是 cards-skills 系統設計的 process-level 元原則。">#81 卡片系統的迭代浮現</a> — 本 reference 的成型過程（spiral 而非線性）</li>
<li><a href="/blog/report/literal-interception-vs-behavioral-refinement/" data-link-title="字面攔截 vs 行為精煉：驗證手段跟錯誤層次的對齊" data-link-desc="驗證手段必須跟錯誤層次對齊：字面錯誤（typo / syntax / 缺欄位）用 hook / lint / CI 攔截；行為錯誤（思考偏差 / 判斷錯位 / collapse 反模式）用 multi-pass spiral 收斂。強行用 hook 蓋行為錯誤 = 給出 false confidence、反而比沒保護危險。本卡是 #72 結構性對策在「驗證粒度」維度的 ceiling — 不是所有錯誤都該被攔截。">#82 字面攔截 vs 行為精煉</a> — 為什麼用 hook 防 collapse 行不通、本 reference 是 multi-pass 設計（self-check 是第二輪、dogfood 是第三輪）</li>
</ul>
]]></content:encoded></item><item><title>Failure Pivot Protocol — 失敗 2 次的轉折協議</title><link>https://tarrragon.github.io/blog/skills/requirement-protocol/failure-pivot-protocol/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/requirement-protocol/failure-pivot-protocol/</guid><description>&lt;p>同方向失敗 ≥ 2 次時的轉折協議 — 停下來驗證底層假設、不沿同方向加碼到第 3 次。&lt;/p>
&lt;p>適用：debug 反覆失敗、CSS 規則不生效、JS 改完元素還原、layout 怎麼調都不對。
不適用：第 1 次失敗（修細節即可）；不同方向各自失敗 1 次（不算同方向累積）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋失敗計數、假設驗證、換方向決策、對外回報模板。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="何時參閱本文件">何時參閱本文件&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>訊號&lt;/th>
 &lt;th>該做的第一件事&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>同方向第 2 次失敗&lt;/td>
 &lt;td>停 — 用工具驗證底層假設&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>內心 OS：「再試一次更小心應該就過」&lt;/td>
 &lt;td>停 — 這是沉沒成本綁住的訊號&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將加 &lt;code>!important&lt;/code> 解 specificity&lt;/td>
 &lt;td>停 — 切到 CSS layers 思路&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將加第 2 條 polyfill 補跨瀏覽器&lt;/td>
 &lt;td>停 — 先回報成本、問使用者意願&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將用 imperative JS 補宣告式 layout&lt;/td>
 &lt;td>停 — 切到 CSS-first 思路&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼第-2-次是轉折點">為什麼第 2 次是轉折點&lt;/h2>
&lt;p>第 1 次失敗常是執行細節（typo、cache、syntax）— 修了再試通常會過。&lt;/p>
&lt;p>第 2 次失敗、用同樣的方法但更小心、還是失敗 — 訊號的重量遠大於兩次相加。它說的是：&lt;strong>「我以為的問題不在這層、根本問題在別處」&lt;/strong>。&lt;/p>
&lt;p>第 3 次以上、沉沒成本綁住、加碼產生的副作用會超過解決的問題：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>嘗試次數&lt;/th>
 &lt;th>心理狀態&lt;/th>
 &lt;th>行動模式&lt;/th>
 &lt;th>副作用&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1&lt;/td>
 &lt;td>信心足&lt;/td>
 &lt;td>直接做&lt;/td>
 &lt;td>無&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2&lt;/td>
 &lt;td>信心動搖&lt;/td>
 &lt;td>加碼（更複雜的 selector / important）&lt;/td>
 &lt;td>可控&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3&lt;/td>
 &lt;td>焦慮&lt;/td>
 &lt;td>全面反擊（layers + important + polyfill）&lt;/td>
 &lt;td>大 — 改動範圍擴張&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4+&lt;/td>
 &lt;td>沉沒成本綁住&lt;/td>
 &lt;td>不肯放棄已寫的&lt;/td>
 &lt;td>嚴重 — 為前面的錯買單&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>第 2 次是還能優雅切換方向的最後機會。&lt;/p>
&lt;hr>
&lt;h2 id="失敗計數的協議">失敗計數的協議&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>失敗次數&lt;/th>
 &lt;th>行動&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>第 1 次&lt;/td>
 &lt;td>修細節（typo、cache、syntax）再試&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>第 2 次&lt;/td>
 &lt;td>&lt;strong>停下來&lt;/strong> — 用工具驗證底層假設（DOM tree、computed style、framework 行為）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>第 2 次驗證後&lt;/td>
 &lt;td>假設對 → 繼續修；假設錯 → 換方向、不為前面買單&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵動作是第 2 次的「停」 — 把行動從「執行更努力」切換到「驗證假設」。&lt;/p>
&lt;hr>
&lt;h2 id="假設驗證的具體方法">假設驗證的具體方法&lt;/h2>
&lt;h3 id="方法-1用工具讀真實狀態">方法 1：用工具讀真實狀態&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>DOM 結構&lt;/td>
 &lt;td>playwright &lt;code>browser_evaluate&lt;/code> 讀 ancestor chain&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Computed style&lt;/td>
 &lt;td>playwright + &lt;code>getComputedStyle()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>元素位置&lt;/td>
 &lt;td>playwright + &lt;code>getBoundingClientRect()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Framework 行為&lt;/td>
 &lt;td>讀框架 source、看 reconciliation 條件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Event 觸發&lt;/td>
 &lt;td>DevTools Event Listeners panel + &lt;code>console.count()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="方法-2反問如果假設錯了會怎樣">方法 2：反問「如果假設錯了會怎樣」&lt;/h3>
&lt;p>這個反思能在沒有工具的情況下測試假設。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>假設&lt;/th>
 &lt;th>如果錯了會發生什麼&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Drawer 是 form 的 sibling&lt;/td>
 &lt;td>那 grid-row 完全無效（drawer 跟 form 共用 grid cell）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Specificity 30 是上限&lt;/td>
 &lt;td>那 layers 才是解、不是雙寫 selector&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>元素永遠存在於 DOM&lt;/td>
 &lt;td>那 framework 重渲染後 querySelector 會回 null&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>「如果錯了會發生什麼」的答案 = 你正在看的失敗現象 → 假設可能錯。&lt;/p></description><content:encoded><![CDATA[<p>同方向失敗 ≥ 2 次時的轉折協議 — 停下來驗證底層假設、不沿同方向加碼到第 3 次。</p>
<p>適用：debug 反覆失敗、CSS 規則不生效、JS 改完元素還原、layout 怎麼調都不對。
不適用：第 1 次失敗（修細節即可）；不同方向各自失敗 1 次（不算同方向累積）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋失敗計數、假設驗證、換方向決策、對外回報模板。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>同方向第 2 次失敗</td>
          <td>停 — 用工具驗證底層假設</td>
      </tr>
      <tr>
          <td>內心 OS：「再試一次更小心應該就過」</td>
          <td>停 — 這是沉沒成本綁住的訊號</td>
      </tr>
      <tr>
          <td>即將加 <code>!important</code> 解 specificity</td>
          <td>停 — 切到 CSS layers 思路</td>
      </tr>
      <tr>
          <td>即將加第 2 條 polyfill 補跨瀏覽器</td>
          <td>停 — 先回報成本、問使用者意願</td>
      </tr>
      <tr>
          <td>即將用 imperative JS 補宣告式 layout</td>
          <td>停 — 切到 CSS-first 思路</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼第-2-次是轉折點">為什麼第 2 次是轉折點</h2>
<p>第 1 次失敗常是執行細節（typo、cache、syntax）— 修了再試通常會過。</p>
<p>第 2 次失敗、用同樣的方法但更小心、還是失敗 — 訊號的重量遠大於兩次相加。它說的是：<strong>「我以為的問題不在這層、根本問題在別處」</strong>。</p>
<p>第 3 次以上、沉沒成本綁住、加碼產生的副作用會超過解決的問題：</p>
<table>
  <thead>
      <tr>
          <th>嘗試次數</th>
          <th>心理狀態</th>
          <th>行動模式</th>
          <th>副作用</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>信心足</td>
          <td>直接做</td>
          <td>無</td>
      </tr>
      <tr>
          <td>2</td>
          <td>信心動搖</td>
          <td>加碼（更複雜的 selector / important）</td>
          <td>可控</td>
      </tr>
      <tr>
          <td>3</td>
          <td>焦慮</td>
          <td>全面反擊（layers + important + polyfill）</td>
          <td>大 — 改動範圍擴張</td>
      </tr>
      <tr>
          <td>4+</td>
          <td>沉沒成本綁住</td>
          <td>不肯放棄已寫的</td>
          <td>嚴重 — 為前面的錯買單</td>
      </tr>
  </tbody>
</table>
<p>第 2 次是還能優雅切換方向的最後機會。</p>
<hr>
<h2 id="失敗計數的協議">失敗計數的協議</h2>
<table>
  <thead>
      <tr>
          <th>失敗次數</th>
          <th>行動</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>第 1 次</td>
          <td>修細節（typo、cache、syntax）再試</td>
      </tr>
      <tr>
          <td>第 2 次</td>
          <td><strong>停下來</strong> — 用工具驗證底層假設（DOM tree、computed style、framework 行為）</td>
      </tr>
      <tr>
          <td>第 2 次驗證後</td>
          <td>假設對 → 繼續修；假設錯 → 換方向、不為前面買單</td>
      </tr>
  </tbody>
</table>
<p>關鍵動作是第 2 次的「停」 — 把行動從「執行更努力」切換到「驗證假設」。</p>
<hr>
<h2 id="假設驗證的具體方法">假設驗證的具體方法</h2>
<h3 id="方法-1用工具讀真實狀態">方法 1：用工具讀真實狀態</h3>
<table>
  <thead>
      <tr>
          <th>假設類型</th>
          <th>驗證工具</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DOM 結構</td>
          <td>playwright <code>browser_evaluate</code> 讀 ancestor chain</td>
      </tr>
      <tr>
          <td>Computed style</td>
          <td>playwright + <code>getComputedStyle()</code></td>
      </tr>
      <tr>
          <td>元素位置</td>
          <td>playwright + <code>getBoundingClientRect()</code></td>
      </tr>
      <tr>
          <td>Framework 行為</td>
          <td>讀框架 source、看 reconciliation 條件</td>
      </tr>
      <tr>
          <td>Event 觸發</td>
          <td>DevTools Event Listeners panel + <code>console.count()</code></td>
      </tr>
  </tbody>
</table>
<h3 id="方法-2反問如果假設錯了會怎樣">方法 2：反問「如果假設錯了會怎樣」</h3>
<p>這個反思能在沒有工具的情況下測試假設。</p>
<table>
  <thead>
      <tr>
          <th>假設</th>
          <th>如果錯了會發生什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Drawer 是 form 的 sibling</td>
          <td>那 grid-row 完全無效（drawer 跟 form 共用 grid cell）</td>
      </tr>
      <tr>
          <td>Specificity 30 是上限</td>
          <td>那 layers 才是解、不是雙寫 selector</td>
      </tr>
      <tr>
          <td>元素永遠存在於 DOM</td>
          <td>那 framework 重渲染後 querySelector 會回 null</td>
      </tr>
  </tbody>
</table>
<p>「如果錯了會發生什麼」的答案 = 你正在看的失敗現象 → 假設可能錯。</p>
<h3 id="方法-3對外回報模板">方法 3：對外回報模板</h3>





<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">我嘗試了 [方向 X]：
</span></span><span class="line"><span class="ln">2</span><span class="cl">- 第 1 次：[做法 A] → [現象]
</span></span><span class="line"><span class="ln">3</span><span class="cl">- 第 2 次：[做法 B] → [一樣的現象]
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">我的底層假設是「[假設 Z]」、但 [方法 1 / 方法 2 的驗證] 顯示 Z 似乎不成立。
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">要不要換 [方向 W]、或您看到我沒看到的訊息嗎？</span></span></code></pre></div><p>對外回報 = 把卡關放到使用者視野、避免繼續單方面加碼。</p>
<hr>
<h2 id="假設錯了之後換方向--全部重寫">假設錯了之後：換方向 ≠ 全部重寫</h2>
<p>換方向不是「之前的全部丟掉」、是「對抗錯假設的部分丟掉、其他保留」。</p>
<p><strong>範例</strong>：search scope UI 放在「form 與 results 之間」。</p>
<ul>
<li>嘗試 1-4：基於假設「drawer 是 form 的 sibling」、用 grid + display:contents + grid-row 排序 → 全失敗</li>
<li>第 5 次（用 playwright 驗證）：drawer 是 form 的 child、跟 form 共用 grid cell</li>
<li>換方向：不用 grid-row 控制位置（被假設綁住的部分）、改用 absolute + drawer margin-top（不被假設綁住）→ 一次成功</li>
</ul>
<p>換方向後保留：CSS variable 命名、scope 命名、HTML 結構。丟掉：grid-row 規則。<strong>只丟跟錯假設綁定的代碼、不丟所有東西</strong>。</p>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1specificity-戰">範例 1：specificity 戰</h3>
<p><strong>錯</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln">1</span><span class="cl"><span class="c">/* 第 1 次：規則沒生效 */</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">.</span><span class="nc">target</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="kc">red</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c">/* 第 2 次：加 specificity */</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">.</span><span class="nc">parent</span> <span class="p">.</span><span class="nc">target</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="kc">red</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c">/* 第 3 次：再加 */</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">.</span><span class="nc">parent</span> <span class="p">.</span><span class="nc">container</span> <span class="p">.</span><span class="nc">target</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="kc">red</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c">/* 第 4 次：放大絕招 */</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">.</span><span class="nc">parent</span> <span class="p">.</span><span class="nc">container</span> <span class="p">.</span><span class="nc">target</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="kc">red</span> <span class="cp">!important</span><span class="p">;</span> <span class="p">}</span></span></span></code></pre></div><p>四次同方向加碼、根本問題（vendor CSS 用了更高 specificity 或更晚 cascade）沒解。</p>
<p><strong>對</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c">/* 第 1 次：規則沒生效 */</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">.</span><span class="nc">target</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="kc">red</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c">/* 第 2 次失敗 → 停下來驗證假設 */</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c">/* DevTools Computed → 看到 vendor 的 .pagefind .target { color: blue } 贏了 */</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c">/* 假設「我的規則該贏」錯 → 換方向：CSS layers */</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">@</span><span class="k">layer</span> <span class="nt">vendor</span> <span class="p">{</span> <span class="c">/* @import vendor css here */</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c">/* 我的規則 unlayered → 自動贏所有 layered 規則 */</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">.</span><span class="nc">target</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="kc">red</span><span class="p">;</span> <span class="p">}</span></span></span></code></pre></div><h3 id="範例-2js-改完元素被還原">範例 2：JS 改完元素被還原</h3>
<p><strong>錯</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 第 1 次：改完被還原
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="nx">el</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="s1">&#39;custom&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// 第 2 次：加保護
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span><span class="nx">setTimeout</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="s1">&#39;custom&#39;</span><span class="p">;</span> <span class="p">},</span> <span class="mi">100</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// 第 3 次：再加
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="nx">setInterval</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="s1">&#39;custom&#39;</span><span class="p">;</span> <span class="p">},</span> <span class="mi">50</span><span class="p">);</span>  <span class="c1">// CPU 100%
</span></span></span></code></pre></div><p><strong>對</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 第 1 次：改完被還原
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="nx">el</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="s1">&#39;custom&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// 第 2 次失敗 → 停、驗證假設
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">// playwright: 看到 framework 每次 state change 重渲染整個子樹
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">// 假設「我的修改會 stick」錯 → 換方向：把客製 UI 放到 framework 邊界外
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kr">const</span> <span class="nx">customEl</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;div&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">customEl</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="s1">&#39;custom&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">container</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">customEl</span><span class="p">);</span>  <span class="c1">// 不在 framework 子樹內、不會被 reconcile
</span></span></span></code></pre></div><hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>第 2 次失敗時、用這份清單檢查：</p>
<ul>
<li><input disabled="" type="checkbox"> 我有沒有列出「底層假設是什麼」？</li>
<li><input disabled="" type="checkbox"> 我有沒有用工具或反問驗證假設？</li>
<li><input disabled="" type="checkbox"> 如果假設錯了、有沒有列出替代方向？</li>
<li><input disabled="" type="checkbox"> 對外回報訊息有沒有寫「驗證 X、似乎不成立、要不要換 W」這種句式？</li>
<li><input disabled="" type="checkbox"> 我有沒有避免「再試一次更小心」這種同方向加碼的衝動？</li>
</ul>
<p>任一項打勾失敗 → 停下來補上、再決定下一步。</p>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code>）：</p>
<ul>
<li><a href="/blog/report/failure-direction-pivot-point/" data-link-title="同方向反覆失敗的轉折點" data-link-desc="第 2 次同方向失敗就停下來回報「假設可能錯了、要不要換思路」、不要等第 4 次失敗才被使用者打斷。本文展開失敗計數與方向切換的判斷。">failure-direction-pivot-point</a> — 同方向反覆失敗的轉折點</li>
<li><a href="/blog/report/two-occurrence-threshold/" data-link-title="2 次門檻：第一次是運氣、第二次是訊號" data-link-desc="同一個問題出現第 2 次時、就該停下來把處理層級升一階 — 從推理升到量測、從手動驗證升到自動化、從同方向嘗試升到換思路。第 1 次失敗的資訊不足、第 2 次提供「重複出現」的證據、值得付出升級成本。本文是 #11 / #15 / #20 / #23 四篇實作的共同抽象。">two-occurrence-threshold</a> — 2 次門檻的抽象原則（跨工具 / 測試 / 思路 / 溝通四面向）</li>
<li><a href="/blog/report/verification-method-timing/" data-link-title="驗證方法的選擇時機" data-link-desc="靜態 CSS 推理 ≥ 2 次失敗就主動提『啟個 server、用 playwright 看 live DOM 比較快』、不要繼續猜。本文展開驗證工具的引入時機。">verification-method-timing</a> — 驗證方法的選擇時機</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.1.0</p>
]]></content:encoded></item><item><title>Progressive Verification — 漸進驗證與最小必要範圍</title><link>https://tarrragon.github.io/blog/skills/requirement-protocol/progressive-verification/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/requirement-protocol/progressive-verification/</guid><description>&lt;p>從最小可驗證單位起步、加變數一次只加一個、範圍從窄到寬擴張。&lt;/p>
&lt;p>適用：UI layout debug、對齊問題、selector / MutationObserver root / JS 操作邊界的設計。
不適用：純內部演算法（沒有視覺、沒有範圍選擇）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋 placeholder 漸進、measurement 完整性、最小必要範圍三個共生原則。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;blockquote>
&lt;p>&lt;strong>Test-First 補充&lt;/strong>：當「漸進」的方式是「寫測試固化」時、必須走 RED → GREEN 兩個訊號才算驗證 — 詳見 &lt;a href="https://tarrragon.github.io/blog/report/test-first-red-before-green/" data-link-title="Test-First：先看到 RED 才相信 GREEN" data-link-desc="一個只看過 GREEN 的測試是「未驗證的訊號」、不是「會抓回歸的測試」。必須先在「該失敗的版本」上看到 RED、再在「該通過的版本」上看到 GREEN — 兩次跑都對、才能相信測試真的 catch 到該 catch 的東西。跳過 RED 等於把驗收標準降到「跑得通」、漏掉「測試自己有沒有 bug」這層。">#69 Test-First：先看到 RED 才相信 GREEN&lt;/a>。沒看過 RED 的測試 = 未驗證的訊號、不能信任。&lt;/p>&lt;/blockquote>
&lt;h2 id="何時參閱本文件">何時參閱本文件&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>訊號&lt;/th>
 &lt;th>該做的第一件事&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>開始 UI layout debug、不知道從哪一步起&lt;/td>
 &lt;td>從色塊 placeholder 起步&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>對齊規則寫了結果歪掉、不知道哪裡錯&lt;/td>
 &lt;td>列方程組、確認每個變數有來源&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>設計 selector / observer / JS 操作的範圍&lt;/td>
 &lt;td>從最小起、有證據再擴張&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>想用 &lt;code>document.querySelectorAll('*')&lt;/code> 或 &lt;code>subtree: true&lt;/code>&lt;/td>
 &lt;td>停 — 範圍可能過寬、補上限制條件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Layout debug 一次改了 5 個變數、改完不知道哪個生效&lt;/td>
 &lt;td>退回去、一次只動一個&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼這三個原則合併在一份-reference">為什麼這三個原則合併在一份 reference&lt;/h2>
&lt;p>三個原則服務同一個讀者群體（&lt;strong>正在開始一個新工作、還沒卡關&lt;/strong>）、回答同一類問題（&lt;strong>該從多大的範圍 / 多少變數起步&lt;/strong>）。&lt;/p>
&lt;ul>
&lt;li>Placeholder 漸進 = 視覺面的「一次一個變數」&lt;/li>
&lt;li>Measurement 完整性 = 對齊問題的「方程組必須完整」&lt;/li>
&lt;li>Minimum scope = JS / CSS 範圍的「窄起來再放寬」&lt;/li>
&lt;/ul>
&lt;p>共同精神：&lt;strong>先窄後寬、有證據再擴張&lt;/strong>。「先寬後縮」的問題是分不出哪個寬度是刻意的；「先窄後寬」每次擴張都有原因可追。&lt;/p>
&lt;hr>
&lt;h2 id="原則-1placeholder-漸進除錯">原則 1：Placeholder 漸進除錯&lt;/h2>
&lt;p>UI debug 從色塊起步、加東西一次加一個。&lt;/p>
&lt;h3 id="起步純色塊">起步：純色塊&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">style&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;width: 200px; height: 100px; background: red; border: 2px solid black;&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>沒文字、沒樣式、沒互動。&lt;strong>唯一目的&lt;/strong>：確認位置、尺寸、grid / flex / absolute 的定位邏輯對。&lt;/p>
&lt;h3 id="階段順序">階段順序&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>1&lt;/td>
 &lt;td>純色塊（固定尺寸 + 顯眼邊框）&lt;/td>
 &lt;td>位置、grid cell、stacking 對&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2&lt;/td>
 &lt;td>占位文字（單行、無樣式）&lt;/td>
 &lt;td>文字基線對、line-height 沒影響&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3&lt;/td>
 &lt;td>真實內容（多行、含長字串）&lt;/td>
 &lt;td>換行、溢出、文字裁切對&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4&lt;/td>
 &lt;td>視覺樣式（color、font、padding）&lt;/td>
 &lt;td>視覺層次對&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>5&lt;/td>
 &lt;td>互動行為（hover、click、focus）&lt;/td>
 &lt;td>互動狀態對、focus 不跑掉&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每階段只引入一個變數、發現問題能立刻定位。&lt;strong>跳階段&lt;/strong> = 失敗時不知道是哪個變數錯。&lt;/p>
&lt;h3 id="典型反例">典型反例&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c">&amp;lt;!-- 第 1 步直接寫真實內容 + 完整樣式 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;card&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h3&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Search results&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h3&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Showing {{count}} matches for &amp;#34;{{query}}&amp;#34;&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ul&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>...&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">ul&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>CSS 寫了 30 條、結果 &lt;code>.card&lt;/code> 沒在預期位置。是 grid 錯？font-size 影響？margin-collapse？line-height？無法定位。&lt;/p>
&lt;hr>
&lt;h2 id="原則-2measurement-完整性">原則 2：Measurement 完整性&lt;/h2>
&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">target_y = anchor_y + offset
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">total_height = h1_height + form_height + gap + scope_height + ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每個變數都要有明確來源 — 任一個未知 → 整組無解。&lt;/p></description><content:encoded><![CDATA[<p>從最小可驗證單位起步、加變數一次只加一個、範圍從窄到寬擴張。</p>
<p>適用：UI layout debug、對齊問題、selector / MutationObserver root / JS 操作邊界的設計。
不適用：純內部演算法（沒有視覺、沒有範圍選擇）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋 placeholder 漸進、measurement 完整性、最小必要範圍三個共生原則。</p></blockquote>
<hr>
<blockquote>
<p><strong>Test-First 補充</strong>：當「漸進」的方式是「寫測試固化」時、必須走 RED → GREEN 兩個訊號才算驗證 — 詳見 <a href="/blog/report/test-first-red-before-green/" data-link-title="Test-First：先看到 RED 才相信 GREEN" data-link-desc="一個只看過 GREEN 的測試是「未驗證的訊號」、不是「會抓回歸的測試」。必須先在「該失敗的版本」上看到 RED、再在「該通過的版本」上看到 GREEN — 兩次跑都對、才能相信測試真的 catch 到該 catch 的東西。跳過 RED 等於把驗收標準降到「跑得通」、漏掉「測試自己有沒有 bug」這層。">#69 Test-First：先看到 RED 才相信 GREEN</a>。沒看過 RED 的測試 = 未驗證的訊號、不能信任。</p></blockquote>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>開始 UI layout debug、不知道從哪一步起</td>
          <td>從色塊 placeholder 起步</td>
      </tr>
      <tr>
          <td>對齊規則寫了結果歪掉、不知道哪裡錯</td>
          <td>列方程組、確認每個變數有來源</td>
      </tr>
      <tr>
          <td>設計 selector / observer / JS 操作的範圍</td>
          <td>從最小起、有證據再擴張</td>
      </tr>
      <tr>
          <td>想用 <code>document.querySelectorAll('*')</code> 或 <code>subtree: true</code></td>
          <td>停 — 範圍可能過寬、補上限制條件</td>
      </tr>
      <tr>
          <td>Layout debug 一次改了 5 個變數、改完不知道哪個生效</td>
          <td>退回去、一次只動一個</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼這三個原則合併在一份-reference">為什麼這三個原則合併在一份 reference</h2>
<p>三個原則服務同一個讀者群體（<strong>正在開始一個新工作、還沒卡關</strong>）、回答同一類問題（<strong>該從多大的範圍 / 多少變數起步</strong>）。</p>
<ul>
<li>Placeholder 漸進 = 視覺面的「一次一個變數」</li>
<li>Measurement 完整性 = 對齊問題的「方程組必須完整」</li>
<li>Minimum scope = JS / CSS 範圍的「窄起來再放寬」</li>
</ul>
<p>共同精神：<strong>先窄後寬、有證據再擴張</strong>。「先寬後縮」的問題是分不出哪個寬度是刻意的；「先窄後寬」每次擴張都有原因可追。</p>
<hr>
<h2 id="原則-1placeholder-漸進除錯">原則 1：Placeholder 漸進除錯</h2>
<p>UI debug 從色塊起步、加東西一次加一個。</p>
<h3 id="起步純色塊">起步：純色塊</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;width: 200px; height: 100px; background: red; border: 2px solid black;&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></span></span></code></pre></div><p>沒文字、沒樣式、沒互動。<strong>唯一目的</strong>：確認位置、尺寸、grid / flex / absolute 的定位邏輯對。</p>
<h3 id="階段順序">階段順序</h3>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>加入</th>
          <th>驗證</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>純色塊（固定尺寸 + 顯眼邊框）</td>
          <td>位置、grid cell、stacking 對</td>
      </tr>
      <tr>
          <td>2</td>
          <td>占位文字（單行、無樣式）</td>
          <td>文字基線對、line-height 沒影響</td>
      </tr>
      <tr>
          <td>3</td>
          <td>真實內容（多行、含長字串）</td>
          <td>換行、溢出、文字裁切對</td>
      </tr>
      <tr>
          <td>4</td>
          <td>視覺樣式（color、font、padding）</td>
          <td>視覺層次對</td>
      </tr>
      <tr>
          <td>5</td>
          <td>互動行為（hover、click、focus）</td>
          <td>互動狀態對、focus 不跑掉</td>
      </tr>
  </tbody>
</table>
<p>每階段只引入一個變數、發現問題能立刻定位。<strong>跳階段</strong> = 失敗時不知道是哪個變數錯。</p>
<h3 id="典型反例">典型反例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="c">&lt;!-- 第 1 步直接寫真實內容 + 完整樣式 --&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;card&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="p">&lt;</span><span class="nt">h3</span><span class="p">&gt;</span>Search results<span class="p">&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Showing {{count}} matches for &#34;{{query}}&#34;<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>...<span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></span></span></code></pre></div><p>CSS 寫了 30 條、結果 <code>.card</code> 沒在預期位置。是 grid 錯？font-size 影響？margin-collapse？line-height？無法定位。</p>
<hr>
<h2 id="原則-2measurement-完整性">原則 2：Measurement 完整性</h2>
<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">target_y = anchor_y + offset
</span></span><span class="line"><span class="ln">2</span><span class="cl">total_height = h1_height + form_height + gap + scope_height + ...</span></span></code></pre></div><p>每個變數都要有明確來源 — 任一個未知 → 整組無解。</p>
<h3 id="變數來源的三種類型">變數來源的三種類型</h3>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>說明</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Hardcoded</td>
          <td>寫死在 design token / config</td>
          <td><code>--gap: 16px</code>、<code>--h1-height: 48px</code></td>
      </tr>
      <tr>
          <td>Component hook</td>
          <td>框架 / vendor 提供的 API</td>
          <td><code>pagefind.options.height</code>、CSS var</td>
      </tr>
      <tr>
          <td>Runtime measured</td>
          <td>JS 執行時量測（getBoundingClientRect）</td>
          <td><code>form.getBoundingClientRect().height</code></td>
      </tr>
  </tbody>
</table>
<h3 id="反例靠估值補方程式">反例：靠估值補方程式</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln">1</span><span class="cl"><span class="c">/* 假設 form 大概 60px、加 gap 20px、總共 80px */</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">.</span><span class="nc">scope</span> <span class="p">{</span> <span class="k">top</span><span class="p">:</span> <span class="mi">80</span><span class="kt">px</span><span class="p">;</span> <span class="p">}</span></span></span></code></pre></div><p>實際 form 高度是 72px（隨字型 / line-height 變動）→ scope 跑位 8px。</p>
<h3 id="對例每個變數有來源">對例：每個變數有來源</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">formHeight</span> <span class="o">=</span> <span class="nx">form</span><span class="p">.</span><span class="nx">getBoundingClientRect</span><span class="p">().</span><span class="nx">height</span><span class="p">;</span>  <span class="c1">// measured
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">gap</span> <span class="o">=</span> <span class="nb">parseFloat</span><span class="p">(</span><span class="nx">getComputedStyle</span><span class="p">(</span><span class="nx">form</span><span class="p">).</span><span class="nx">marginBottom</span><span class="p">);</span>  <span class="c1">// measured
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span><span class="nx">scope</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">top</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">formHeight</span> <span class="o">+</span> <span class="nx">gap</span><span class="si">}</span><span class="sb">px`</span><span class="p">;</span></span></span></code></pre></div><p>或全部用 design token：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">.</span><span class="nc">scope</span> <span class="p">{</span> <span class="k">top</span><span class="p">:</span> <span class="nb">calc</span><span class="p">(</span><span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">form</span><span class="o">-</span><span class="n">height</span><span class="p">)</span> <span class="o">+</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">gap</span><span class="p">));</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c">/* var 在某處有單一定義、不是分散估值 */</span></span></span></code></pre></div><p>混搭策略要全選同一邊：對齊基準上要嘛全寫死、要嘛全量測、不要 hardcoded + 估值混用。</p>
<hr>
<h2 id="原則-3minimum-necessary-scope">原則 3：Minimum Necessary Scope</h2>
<p>Selector / MutationObserver / JS 操作的範圍從最小起、擴張要有證據。</p>
<h3 id="selector-範圍">Selector 範圍</h3>
<table>
  <thead>
      <tr>
          <th>寬度</th>
          <th>範例</th>
          <th>風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>最小（精準）</td>
          <td><code>#search-form .scope-toggle</code></td>
          <td>安全、變化時要更新 selector</td>
      </tr>
      <tr>
          <td>中等</td>
          <td><code>.scope-toggle</code></td>
          <td>可能命中其他頁面的同名元素</td>
      </tr>
      <tr>
          <td>過寬</td>
          <td><code>[class*=&quot;scope&quot;]</code> / <code>* &gt; .toggle</code></td>
          <td>命中無關元素、副作用未知</td>
      </tr>
  </tbody>
</table>
<p>預設用最小、有證據（多個地方確實要 match）再擴張。</p>
<h3 id="mutationobserver-範圍">MutationObserver 範圍</h3>
<p>三個維度：root、options、頻率。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 過寬
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="nx">observer</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span> <span class="p">{</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">subtree</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">attributes</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// → 監聽整個 page、每個 attribute 變動都觸發、CPU 100%
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// 最小
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="nx">observer</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nx">searchForm</span><span class="p">,</span> <span class="p">{</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// → 只監聽 form 直接子節點變動
</span></span></span></code></pre></div><h3 id="js-操作邊界">JS 操作邊界</h3>
<p>改一個元素的範圍從小到大：</p>
<table>
  <thead>
      <tr>
          <th>範圍</th>
          <th>風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>改 inline style</td>
          <td>安全、僅自家管的元素</td>
      </tr>
      <tr>
          <td>改 attribute</td>
          <td>中 — framework 可能 reconcile 清掉</td>
      </tr>
      <tr>
          <td>改 textContent</td>
          <td>中 — 同上</td>
      </tr>
      <tr>
          <td>改 innerHTML</td>
          <td>高 — 子節點全重建、event listener 失效</td>
      </tr>
      <tr>
          <td>reparent 整節點</td>
          <td>高但可控 — 整節點搬遷、framework 通常不會還原</td>
      </tr>
  </tbody>
</table>
<p>從「改 inline style」起步、不行才升級。</p>
<hr>
<h2 id="三個原則的共同精神">三個原則的共同精神</h2>
<p><strong>從最小可驗證單位起步、有證據再擴張</strong>：</p>
<ul>
<li>Placeholder：色塊 → 文字 → 樣式（一次加一層）</li>
<li>Measurement：每個變數先確認來源、再寫對齊規則</li>
<li>Scope：最窄的 selector / observer / JS 邊界、要擴張要有具體 case</li>
</ul>
<p>「先寬後縮」的反模式：寫一個包山包海的 selector、之後試著加 <code>:not(...)</code> 排除 → 永遠不知道哪些 match 是刻意的。</p>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1ui-debug-起步">範例 1：UI debug 起步</h3>
<blockquote>
<p>任務：把搜尋結果卡片做成兩欄 grid</p></blockquote>
<p><strong>錯</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c">&lt;!-- 直接寫完整版本 --&gt;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;results-grid&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="p">&lt;</span><span class="nt">article</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;result-card&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="p">&lt;</span><span class="nt">h3</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;...&#34;</span><span class="p">&gt;</span>Title<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="p">&lt;</span><span class="nt">p</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;excerpt&#34;</span><span class="p">&gt;</span>{{excerpt}}<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;meta&#34;</span><span class="p">&gt;&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;tag&#34;</span><span class="p">&gt;</span>tag<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> · <span class="p">&lt;</span><span class="nt">time</span><span class="p">&gt;</span>date<span class="p">&lt;/</span><span class="nt">time</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="p">&lt;/</span><span class="nt">article</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">.</span><span class="nc">results-grid</span> <span class="p">{</span> <span class="k">display</span><span class="p">:</span> <span class="k">grid</span><span class="p">;</span> <span class="k">grid-template-columns</span><span class="p">:</span> <span class="mi">1</span><span class="n">fr</span> <span class="mi">1</span><span class="n">fr</span><span class="p">;</span> <span class="k">gap</span><span class="p">:</span> <span class="mi">24</span><span class="kt">px</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">.</span><span class="nc">result-card</span> <span class="p">{</span> <span class="k">padding</span><span class="p">:</span> <span class="mi">16</span><span class="kt">px</span><span class="p">;</span> <span class="k">border</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span> <span class="kc">solid</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">.</span><span class="nc">result-card</span> <span class="nt">h3</span> <span class="p">{</span> <span class="k">font-size</span><span class="p">:</span> <span class="mi">18</span><span class="kt">px</span><span class="p">;</span> <span class="k">margin-bottom</span><span class="p">:</span> <span class="mi">8</span><span class="kt">px</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c">/* ... */</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span></span></span></code></pre></div><p>跑出來、卡片高度不一致、<code>grid-auto-rows</code> 沒設、第二欄擠到第一欄底下。debug 困難 — 是 grid 設定錯？卡片內容差異？margin-collapse？</p>
<p><strong>對</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="c">&lt;!-- 階段 1：純色塊驗證 grid --&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;results-grid&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;height: 100px; background: red;&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;height: 100px; background: blue;&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;height: 100px; background: green;&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;height: 100px; background: yellow;&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></span></span></code></pre></div><p>確認 grid 兩欄正常後、再進階段 2（加占位文字）。</p>
<h3 id="範例-2mutationobserver-root">範例 2：MutationObserver root</h3>
<blockquote>
<p>任務：當 search results 出現時、注入客製 UI</p></blockquote>
<p><strong>錯</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(...).</span><span class="nx">observe</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span> <span class="p">{</span> <span class="nx">subtree</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// 整個 page 任何變動都觸發、callback 跑 1000+ 次/秒
</span></span></span></code></pre></div><p><strong>對</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">container</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.pagefind-ui__results-area&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(...).</span><span class="nx">observe</span><span class="p">(</span><span class="nx">container</span><span class="p">,</span> <span class="p">{</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// 只監聽 results area 的直接子節點變動
</span></span></span></code></pre></div><p>如果之後發現 <code>.pagefind-ui__results-area</code> 內部 nested 變動也要監聽 → 那時再加 <code>subtree: true</code>、加之前能說出「為什麼需要」。</p>
<hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>開始一個新工作前：</p>
<ul>
<li><input disabled="" type="checkbox"> UI debug：第 1 階段是不是純色塊（沒文字、沒樣式）？</li>
<li><input disabled="" type="checkbox"> 對齊規則寫之前：是不是每個變數都列出來源（hardcoded / hook / measured）？</li>
<li><input disabled="" type="checkbox"> Selector：起步是不是最精準的版本？</li>
<li><input disabled="" type="checkbox"> MutationObserver：root / options 是不是最窄的？</li>
<li><input disabled="" type="checkbox"> JS 改元素：是不是從「改 inline style」起、不行才升級？</li>
</ul>
<p>任一項打勾失敗 → 退回最小、重新起步。</p>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code>）：</p>
<ul>
<li><a href="/blog/report/placeholder-driven-ui-debug/" data-link-title="從色塊 placeholder 開始的漸進式 UI 除錯" data-link-desc="UI 除錯的最小可驗證單位是『一個有顏色的盒子』 — 版型先用色塊確認、內容後填。本文說明為什麼漸進式驗證比一次組裝完整 UI 容易 debug。">placeholder-driven-ui-debug</a> — 從色塊 placeholder 開始的漸進式 UI 除錯</li>
<li><a href="/blog/report/measurement-completeness/" data-link-title="量測值缺一不可：依賴未測量值會錯位" data-link-desc="對齊本質是『同一條基準線在多個元素上重現』 — 任何一個元素的高度沒有確定值、整條線都靠不住。本文展開『把對齊問題當線性方程組』的角度。">measurement-completeness</a> — 量測值缺一不可</li>
<li><a href="/blog/report/minimum-necessary-scope-is-sanity-defense/" data-link-title="最小必要範圍是 sanity 防線：保護行為可預測性" data-link-desc="縮 selector 範圍、observer 範圍、JS 操作範圍 — 不是為了效能、是為了讓行為可預測、不被未來變動打破。本文是 #13 / #14 / #29 三篇實作的共同抽象。">minimum-necessary-scope-is-sanity-defense</a> — 最小必要範圍是 sanity 防線</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.1.0</p>
]]></content:encoded></item><item><title>Requirement Protocol — SKILL 入口</title><link>https://tarrragon.github.io/blog/skills/requirement-protocol/skill/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/requirement-protocol/skill/</guid><description>&lt;p>從需求確認到實作的對話協議。把「使用者下指令 → 執行者實作」之間的溝通流程結構化、避免反覆失敗、避免做出使用者沒要的東西、避免在錯誤方向上累積沉沒成本。&lt;/p>
&lt;p>協議的核心命題：&lt;strong>對話成本與重做風險之間有最佳化空間&lt;/strong>。全自決對話成本最低、但容易做錯；全確認重做風險最低、但對話爆炸。協議定的是「哪些該攤、哪些自決」、以及「卡住時該怎麼轉彎」。&lt;/p>
&lt;hr>
&lt;h2 id="core-pillars支柱">Core Pillars（支柱）&lt;/h2>
&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;strong>Visibility-Based Confirmation&lt;/strong> 可見性確認&lt;/td>
 &lt;td>使用者會看到的決定（數字 / 順序 / 文字）攤開確認、純技術細節自決&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Two-Occurrence Threshold&lt;/strong> 2 次門檻&lt;/td>
 &lt;td>第 1 次是運氣、第 2 次是訊號；同方向失敗 2 次就停、不沿同方向加碼到 3&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Cost Transparency&lt;/strong> 成本透明&lt;/td>
 &lt;td>覆寫深度、revert 影響、最小必要範圍 — 把成本攤開讓使用者參與決策&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Multi-pass Refinement&lt;/strong> 多輪精煉&lt;/td>
 &lt;td>第 1 輪實作不追求完美、預期會有未發現問題；設計第 2 / 3 輪用不同 frame 收斂、不是「再仔細一次」、是換角度看（&lt;a href="https://tarrragon.github.io/blog/report/literal-interception-vs-behavioral-refinement/" data-link-title="字面攔截 vs 行為精煉：驗證手段跟錯誤層次的對齊" data-link-desc="驗證手段必須跟錯誤層次對齊：字面錯誤（typo / syntax / 缺欄位）用 hook / lint / CI 攔截；行為錯誤（思考偏差 / 判斷錯位 / collapse 反模式）用 multi-pass spiral 收斂。強行用 hook 蓋行為錯誤 = 給出 false confidence、反而比沒保護危險。本卡是 #72 結構性對策在「驗證粒度」維度的 ceiling — 不是所有錯誤都該被攔截。">#82&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/report/methodology-multi-pass-embedding/" data-link-title="Methodology 的 multi-pass 該升級為 pillar 層：核心結構才會被執行" data-link-desc="任何「教做事方法」的 methodology / SKILL / playbook、應該把 multi-pass refinement 放在 pillar / 核心原則層、不是放在末尾「附帶提醒」段。Pillar 層 = 結構性必跑、appendix 層 = 看心情選擇 = 永遠不跑。本卡是 #82 行為驗證 &amp;#43; #72 結構性對策在「方法論設計本身」這一層的展現。">#85&lt;/a>）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="principles原則速查">Principles（原則速查）&lt;/h2>
&lt;p>讀者在本區塊能完成大方向判斷；具體情境的展開（步驟 / 模板 / 反例）依下方「觸發路由」進對應 reference。&lt;/p>
&lt;h3 id="1-可決定-vs-該確認的邊界">1. 可決定 vs 該確認的邊界&lt;/h3>
&lt;p>純技術實作（grid / flex、ResizeObserver / setInterval、selector 寫法）可自決；使用者會看到的決定（breakpoint、預設尺寸、filter 順序、UI 文字、配色）先列選項給使用者點頭。&lt;/p>
&lt;p>判準三問：&lt;strong>UI 上會不會產生使用者感知的差異？選不同會不會影響體驗？寫進 commit 後改動成本高不高？&lt;/strong> 任一個「是」 → 該確認。確認時給「選項 + 推薦 + 開放修改」、不要開放問。&lt;/p>
&lt;h3 id="2-同方向失敗-2-次--停下驗證假設">2. 同方向失敗 2 次 = 停下驗證假設&lt;/h3>
&lt;p>第 1 次失敗多半是執行細節（typo、cache、syntax）— 修了再試。第 2 次同方向失敗、不要再試一次更小心、用工具驗證底層假設（DOM tree、computed style、framework 行為）。&lt;/p>
&lt;p>驗證後分兩條路：&lt;strong>假設對 → 繼續修；假設錯 → 換方向、不為前面的努力買單&lt;/strong>。第 3 次同方向加碼（更複雜的 selector、加 &lt;code>!important&lt;/code>、再寫一層 polyfill）會放大原本的問題、產生脆弱的 patchwork。&lt;/p>
&lt;h3 id="3-推理失敗-2-次切到量測工具">3. 推理失敗 2 次切到量測工具&lt;/h3>
&lt;p>靜態 CSS 推理 + 視覺截圖溝通的迴圈在第 1 次假設錯了之後成本就爆炸。第 2 次失敗主動提：&lt;strong>起 server、用 playwright &lt;code>browser_evaluate&lt;/code> 讀 live DOM&lt;/strong>。&lt;/p>
&lt;p>工具切換 ROI 在第 1 次失敗後就轉正、不要等到第 5 次。簡單一次性確認用 DevTools、複雜或反覆 debug 用 playwright（可重跑、可寫成測試）。&lt;/p>
&lt;h3 id="4-覆寫成本攤開不偷偷對抗">4. 覆寫成本攤開、不偷偷對抗&lt;/h3>
&lt;p>當客製需求看似簡單但會對抗多層（UA stylesheet、framework CSS、browser default）— 在開始寫之前先報成本：&lt;strong>「會打到哪幾層、要寫幾條規則、剩下什麼風險（升級會壞？瀏覽器差異？）」&lt;/strong>、讓使用者決定值不值。&lt;/p></description><content:encoded><![CDATA[<p>從需求確認到實作的對話協議。把「使用者下指令 → 執行者實作」之間的溝通流程結構化、避免反覆失敗、避免做出使用者沒要的東西、避免在錯誤方向上累積沉沒成本。</p>
<p>協議的核心命題：<strong>對話成本與重做風險之間有最佳化空間</strong>。全自決對話成本最低、但容易做錯；全確認重做風險最低、但對話爆炸。協議定的是「哪些該攤、哪些自決」、以及「卡住時該怎麼轉彎」。</p>
<hr>
<h2 id="core-pillars支柱">Core Pillars（支柱）</h2>
<table>
  <thead>
      <tr>
          <th>支柱</th>
          <th>意義</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Visibility-Based Confirmation</strong> 可見性確認</td>
          <td>使用者會看到的決定（數字 / 順序 / 文字）攤開確認、純技術細節自決</td>
      </tr>
      <tr>
          <td><strong>Two-Occurrence Threshold</strong> 2 次門檻</td>
          <td>第 1 次是運氣、第 2 次是訊號；同方向失敗 2 次就停、不沿同方向加碼到 3</td>
      </tr>
      <tr>
          <td><strong>Cost Transparency</strong> 成本透明</td>
          <td>覆寫深度、revert 影響、最小必要範圍 — 把成本攤開讓使用者參與決策</td>
      </tr>
      <tr>
          <td><strong>Multi-pass Refinement</strong> 多輪精煉</td>
          <td>第 1 輪實作不追求完美、預期會有未發現問題；設計第 2 / 3 輪用不同 frame 收斂、不是「再仔細一次」、是換角度看（<a href="/blog/report/literal-interception-vs-behavioral-refinement/" data-link-title="字面攔截 vs 行為精煉：驗證手段跟錯誤層次的對齊" data-link-desc="驗證手段必須跟錯誤層次對齊：字面錯誤（typo / syntax / 缺欄位）用 hook / lint / CI 攔截；行為錯誤（思考偏差 / 判斷錯位 / collapse 反模式）用 multi-pass spiral 收斂。強行用 hook 蓋行為錯誤 = 給出 false confidence、反而比沒保護危險。本卡是 #72 結構性對策在「驗證粒度」維度的 ceiling — 不是所有錯誤都該被攔截。">#82</a> / <a href="/blog/report/methodology-multi-pass-embedding/" data-link-title="Methodology 的 multi-pass 該升級為 pillar 層：核心結構才會被執行" data-link-desc="任何「教做事方法」的 methodology / SKILL / playbook、應該把 multi-pass refinement 放在 pillar / 核心原則層、不是放在末尾「附帶提醒」段。Pillar 層 = 結構性必跑、appendix 層 = 看心情選擇 = 永遠不跑。本卡是 #82 行為驗證 &#43; #72 結構性對策在「方法論設計本身」這一層的展現。">#85</a>）</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="principles原則速查">Principles（原則速查）</h2>
<p>讀者在本區塊能完成大方向判斷；具體情境的展開（步驟 / 模板 / 反例）依下方「觸發路由」進對應 reference。</p>
<h3 id="1-可決定-vs-該確認的邊界">1. 可決定 vs 該確認的邊界</h3>
<p>純技術實作（grid / flex、ResizeObserver / setInterval、selector 寫法）可自決；使用者會看到的決定（breakpoint、預設尺寸、filter 順序、UI 文字、配色）先列選項給使用者點頭。</p>
<p>判準三問：<strong>UI 上會不會產生使用者感知的差異？選不同會不會影響體驗？寫進 commit 後改動成本高不高？</strong> 任一個「是」 → 該確認。確認時給「選項 + 推薦 + 開放修改」、不要開放問。</p>
<h3 id="2-同方向失敗-2-次--停下驗證假設">2. 同方向失敗 2 次 = 停下驗證假設</h3>
<p>第 1 次失敗多半是執行細節（typo、cache、syntax）— 修了再試。第 2 次同方向失敗、不要再試一次更小心、用工具驗證底層假設（DOM tree、computed style、framework 行為）。</p>
<p>驗證後分兩條路：<strong>假設對 → 繼續修；假設錯 → 換方向、不為前面的努力買單</strong>。第 3 次同方向加碼（更複雜的 selector、加 <code>!important</code>、再寫一層 polyfill）會放大原本的問題、產生脆弱的 patchwork。</p>
<h3 id="3-推理失敗-2-次切到量測工具">3. 推理失敗 2 次切到量測工具</h3>
<p>靜態 CSS 推理 + 視覺截圖溝通的迴圈在第 1 次假設錯了之後成本就爆炸。第 2 次失敗主動提：<strong>起 server、用 playwright <code>browser_evaluate</code> 讀 live DOM</strong>。</p>
<p>工具切換 ROI 在第 1 次失敗後就轉正、不要等到第 5 次。簡單一次性確認用 DevTools、複雜或反覆 debug 用 playwright（可重跑、可寫成測試）。</p>
<h3 id="4-覆寫成本攤開不偷偷對抗">4. 覆寫成本攤開、不偷偷對抗</h3>
<p>當客製需求看似簡單但會對抗多層（UA stylesheet、framework CSS、browser default）— 在開始寫之前先報成本：<strong>「會打到哪幾層、要寫幾條規則、剩下什麼風險（升級會壞？瀏覽器差異？）」</strong>、讓使用者決定值不值。</p>
<p>不在使用者不知情的情況下堆 <code>!important</code> / specificity 戰 / 多層 polyfill — 沉默對抗會讓使用者驚訝於後續的維護負擔。</p>
<h3 id="5-revert-含-checkpoint不直接清空">5. Revert 含 checkpoint、不直接清空</h3>
<p>收到「先還原」「先重來」「換個方向」時、先確認：<strong>還原到哪個狀態？要不要先 commit 當前進度當 checkpoint（標「explored, not adopted」）？</strong> 再執行 reset。</p>
<p>探索的成果即使沒採用、也是「為什麼不採用」的證據 — 直接清空會丟掉「下次別再走這條路」的判斷依據。</p>
<h3 id="6-漸進驗證最小必要範圍">6. 漸進驗證、最小必要範圍</h3>
<p>UI debug 從色塊 placeholder 起步（沒文字、沒樣式、單純色塊）→ 確認位置 / 尺寸 / grid 對 → 再加文字 → 再加樣式 → 再加互動。每階段只引入一個變數。</p>
<p>Selector / MutationObserver root / JS 操作邊界：<strong>從最小開始、有證據再擴張</strong>。「先寬後縮」分不出哪個寬度是刻意的；「先窄後寬」每次擴張都有原因。</p>
<h3 id="7-multi-pass-refinement第-1-輪不追求完美設計第-2--3-輪用不同-frame">7. Multi-pass Refinement：第 1 輪不追求完美、設計第 2 / 3 輪用不同 frame</h3>
<p>第 1 輪實作預期會有未發現問題、不要追求 perfect — 跑得到結尾、看實際結果比寫得漂亮重要。第 2 輪用「對需求 / 邊界 case」frame、第 3 輪用「dogfood / 反向自查」frame、第 N 輪換「上層原則」frame。每輪不同 frame 才能 catch 上一輪 miss 的東西。</p>
<p>呈現決策時的「五維度展開」（<a href="/blog/skills/requirement-protocol/decision-dialogue/" data-link-title="Decision Dialogue — 決策對話的五維度協議" data-link-desc="requirement-protocol reference：呈現決策時的五個獨立維度（呈現格式 / 策略數 / 批次邊界 / 時間軸 / 選項類型）&#43; 五步判讀 &#43; 完整套用範本 &#43; self-check。對應 #74-#79 系列。"><code>references/decision-dialogue.md</code></a>）就是 multi-pass 在「決策呈現」場景的具體實現：每維度等於一輪 self-check。<strong>「再仔細一次」≠ multi-pass — 同 frame 重看 catch 不到不同層的錯</strong>。L4 review / pair / dogfood 才是行為錯誤的解、不是再寫一條 hook（<a href="/blog/report/literal-interception-vs-behavioral-refinement/" data-link-title="字面攔截 vs 行為精煉：驗證手段跟錯誤層次的對齊" data-link-desc="驗證手段必須跟錯誤層次對齊：字面錯誤（typo / syntax / 缺欄位）用 hook / lint / CI 攔截；行為錯誤（思考偏差 / 判斷錯位 / collapse 反模式）用 multi-pass spiral 收斂。強行用 hook 蓋行為錯誤 = 給出 false confidence、反而比沒保護危險。本卡是 #72 結構性對策在「驗證粒度」維度的 ceiling — 不是所有錯誤都該被攔截。">#82</a>）。</p>
<hr>
<h2 id="when-to-consult-this-skill觸發路由">When to Consult This Skill（觸發路由）</h2>
<table>
  <thead>
      <tr>
          <th>觸發情境</th>
          <th>讀哪份 reference</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>收到模糊指令（含「對齊」「靠近」「隔離」「不要動」「分開」等）</td>
          <td><code>references/clarifying-ambiguous-instructions.md</code></td>
      </tr>
      <tr>
          <td>不確定某個決定該自決還是該先問使用者</td>
          <td><code>references/clarifying-ambiguous-instructions.md</code></td>
      </tr>
      <tr>
          <td>收到「依 X 篩選 / 只看 X / 過濾 Y」類指令、source 是分批的</td>
          <td><code>references/clarifying-ambiguous-instructions.md</code>（類型 5：篩選三問）</td>
      </tr>
      <tr>
          <td>同方向失敗 ≥ 2 次、想再試一次更小心</td>
          <td><code>references/failure-pivot-protocol.md</code></td>
      </tr>
      <tr>
          <td>推理 + 視覺截圖溝通迴圈卡住、不知道該不該換工具</td>
          <td><code>references/tool-switching-timing.md</code></td>
      </tr>
      <tr>
          <td>客製需求要對抗多層（vendor CSS、framework、browser default）</td>
          <td><code>references/cost-and-checkpoint.md</code></td>
      </tr>
      <tr>
          <td>收到「先還原 / 先重來 / 換個方向」類指令</td>
          <td><code>references/cost-and-checkpoint.md</code></td>
      </tr>
      <tr>
          <td>開始 UI layout debug、不知道從哪一步起</td>
          <td><code>references/progressive-verification.md</code></td>
      </tr>
      <tr>
          <td>設計 selector / MutationObserver root / JS 操作範圍</td>
          <td><code>references/progressive-verification.md</code></td>
      </tr>
      <tr>
          <td>準備呈現決策給使用者選擇（A 還是 B、要不要做 X）</td>
          <td><code>references/decision-dialogue.md</code></td>
      </tr>
      <tr>
          <td>寫到「你想怎麼做？」「ABCDE 你選哪個？」這類開放問</td>
          <td><code>references/decision-dialogue.md</code></td>
      </tr>
      <tr>
          <td>反省題 / retrospective / 「下一步往哪走」類問題</td>
          <td><code>references/decision-dialogue.md</code></td>
      </tr>
  </tbody>
</table>
<p>每份 reference 自包含：以該情境為核心、把六大原則翻譯成可直接套用的協議步驟與模板。閱讀任一 reference 不需要回來看其他 reference。</p>
<hr>
<h2 id="success-criteriam1-m2-認知負擔類">Success Criteria（M1-M2 認知負擔類）</h2>
<table>
  <thead>
      <tr>
          <th>Metric</th>
          <th>定義</th>
          <th>目標</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>M1</strong></td>
          <td>從 SKILL.md 出發、解決一個觸發情境需要開幾個檔案</td>
          <td>≤ 2</td>
      </tr>
      <tr>
          <td><strong>M2</strong></td>
          <td>隨機抽一份 reference、不讀其他 reference 能否獨立套用</td>
          <td>100%</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="directory-index">Directory Index</h2>





<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">requirement-protocol/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── SKILL.md                                       # 本檔：四支柱 + 七大原則速查 + 觸發路由
</span></span><span class="line"><span class="ln">3</span><span class="cl">└── references/
</span></span><span class="line"><span class="ln">4</span><span class="cl">    ├── clarifying-ambiguous-instructions.md       # 情境 1：模糊指令的澄清協議（spatial / relative / isolation / decision-authority）
</span></span><span class="line"><span class="ln">5</span><span class="cl">    ├── failure-pivot-protocol.md                  # 情境 2：失敗 2 次的轉折協議（停下、驗證假設、換方向）
</span></span><span class="line"><span class="ln">6</span><span class="cl">    ├── cost-and-checkpoint.md                     # 情境 3：覆寫成本告知 + revert 含 checkpoint
</span></span><span class="line"><span class="ln">7</span><span class="cl">    ├── progressive-verification.md                # 情境 4：placeholder 漸進 + measurement 完整性 + 最小必要範圍
</span></span><span class="line"><span class="ln">8</span><span class="cl">    ├── tool-switching-timing.md                   # 情境 5：推理 / DevTools / playwright 之間的切換時機
</span></span><span class="line"><span class="ln">9</span><span class="cl">    └── decision-dialogue.md                       # 情境 6：呈現決策的五維度協議（呈現 / 策略 / 批次 / 時間 / 選項類型）</span></span></code></pre></div><hr>
<h2 id="reading-order建議閱讀順序">Reading Order（建議閱讀順序）</h2>
<ol>
<li>第一次接觸 → 從本 SKILL.md 的「三大支柱 + 六大原則」讀起</li>
<li>進入實際情境 → 依觸發路由讀對應 reference（只讀一份）</li>
<li>想驗證自己有沒有套用對 → 用該 reference 結尾的 self-check checklist 自評</li>
</ol>
<hr>
<h2 id="相關抽象層原則在-contentreport">相關抽象層原則（在 content/report/）</h2>
<p>本 skill 的協議建立在幾條抽象層原則上、實作協議時可背景引用：</p>
<ul>
<li><a href="/blog/report/two-occurrence-threshold/" data-link-title="2 次門檻：第一次是運氣、第二次是訊號" data-link-desc="同一個問題出現第 2 次時、就該停下來把處理層級升一階 — 從推理升到量測、從手動驗證升到自動化、從同方向嘗試升到換思路。第 1 次失敗的資訊不足、第 2 次提供「重複出現」的證據、值得付出升級成本。本文是 #11 / #15 / #20 / #23 四篇實作的共同抽象。">#42 2 次門檻</a> — 第 1 次失敗是運氣、第 2 次是訊號（六大原則 2/3 的根據）</li>
<li><a href="/blog/report/minimum-necessary-scope-is-sanity-defense/" data-link-title="最小必要範圍是 sanity 防線：保護行為可預測性" data-link-desc="縮 selector 範圍、observer 範圍、JS 操作範圍 — 不是為了效能、是為了讓行為可預測、不被未來變動打破。本文是 #13 / #14 / #29 三篇實作的共同抽象。">#43 最小必要範圍</a> — 範圍從窄起、有證據再擴張（原則 6 的根據）</li>
<li><a href="/blog/report/single-source-of-truth/" data-link-title="Single Source of Truth：值的住址只能有一處" data-link-desc="同一個值（CSS token、視覺基準、runtime 量測）的權威來源只能有一個位置 — 多源時會分歧、會漏改、會讓讀者不知道哪個生效。本文是 #3 / #26 / #27 三篇實作的共同抽象。">#44 SSOT</a> — 值的住址只能一處（成本告知與澄清的共骨）</li>
<li><a href="/blog/report/external-component-collaboration-layers/" data-link-title="跟外部組件合作的層次：離介面越近、合作越穩" data-link-desc="客製外部組件的穩定性與「離組件作者保證的對外介面多遠」成反比。每往內推一層、依賴前提增加、升級風險上升、可逆性下降。本文是 #1 / #5 / #19 / #24 四篇實作的共同抽象。">#45 外部組件合作四層</a> — 離公共介面越近越穩</li>
<li><a href="/blog/report/ease-of-writing-vs-intent-alignment/" data-link-title="寫作便利度跟意圖對齊反相關" data-link-desc="寫程式時最容易寫出的版本、通常是離意圖最遠的版本。便利度建立在「現有上下文 / 已 materialize 資料 / 已存在 API」上、而意圖對齊需要找到正確的層、處理上游、跨抽象層 — 兩者方向相反。識別這個反相關 = 識別自己掉進「容易寫的陷阱」。">#67 寫作便利度跟意圖對齊反相關</a> — 容易寫的位置通常是錯位的位置（meta-principle、解釋為什麼澄清協議能 catch 到便利驅動的錯誤）</li>
<li><a href="/blog/report/verification-timeline-checkpoints/" data-link-title="驗收的時間軸：四個 checkpoint" data-link-desc="驗收不是單一動作、是分散在四個時點（寫之前 / 開發中 / ship 前 / ship 後）的累積判斷。每個 checkpoint 能 catch 不同類型的失敗、成本不同。早期 checkpoint 抓越多、晚期 checkpoint 越輕鬆。實務上常常 collapse 成「寫的時候 &#43; ship 後出問題才修」、跳過寫之前 / ship 前。">#68 驗收的時間軸：四個 checkpoint</a> — 寫之前 / 開發中 / ship 前 / ship 後分散驗收（原則 6 的展開）</li>
<li><a href="/blog/report/test-first-red-before-green/" data-link-title="Test-First：先看到 RED 才相信 GREEN" data-link-desc="一個只看過 GREEN 的測試是「未驗證的訊號」、不是「會抓回歸的測試」。必須先在「該失敗的版本」上看到 RED、再在「該通過的版本」上看到 GREEN — 兩次跑都對、才能相信測試真的 catch 到該 catch 的東西。跳過 RED 等於把驗收標準降到「跑得通」、漏掉「測試自己有沒有 bug」這層。">#69 Test-First：先看到 RED 才相信 GREEN</a> — 「需求確認」最重要的一環：使用者意圖落實成測試 → 在 buggy code 上跑出 RED 才證明測試 catch 到該意圖、再修到 GREEN — 跳過 RED 等於跳過「測試對應到使用者意圖」的驗證</li>
<li><a href="/blog/report/url-as-state-container/" data-link-title="URL 是 stateful UI 的儲存層 — 哪些 state 該寫進 URL" data-link-desc="互動式 UI 的 state 散落在多層（in-memory / URL / localStorage / server / index）、每層有不同特性。可分享 / 可恢復 / 可導航的 state 該寫進 URL — 不寫進 = silent 把這些特性犧牲掉。本文展開「state 的儲存層選擇」協議與 URL 的具體位置。">#70 URL 是 stateful UI 的儲存層</a> — 列「使用者意圖完整集合」要包含 URL 維度：分享 / reload / back-forward 該不該保留 state</li>
<li><a href="/blog/report/tab-order-mental-model-alignment/" data-link-title="Tab Order = DOM Order = Mental Model 三者對齊" data-link-desc="Tab 順序由 DOM 順序決定（除非用 tabindex 強制覆寫）。三者該對齊：DOM 順序、tab 順序、使用者 mental model 的互動順序。三者不一致時、優先重排 DOM 而非用 tabindex — tabindex &gt; 0 是反模式（[#52]）。">#71 Tab Order = DOM Order = Mental Model 三者對齊</a> — A11y 維度的需求澄清：使用者預期「先做 X 再做 Y」、tab 順序該對齊 mental model</li>
<li><a href="/blog/report/external-trigger-for-high-roi-work/" data-link-title="高 ROI 無外部觸發的工作會被結構性跳過" data-link-desc="工作有兩個獨立維度：ROI 高低 &#43; 是否有外部觸發。高 ROI &#43; 無觸發 = ROI 的承諾、拖延的現實。靠紀律不可行 — 結構性偏差需要結構性對策（外部觸發 / CI / hook / 排程 / pair）。本卡是 #67 便利反相關、#68 checkpoint 跳過、#69 RED 跳過的共同上位原則。">#72 高 ROI 無外部觸發的工作會被結構性跳過</a> — meta-原則：澄清 / Checkpoint 1 / RED phase 都是「沒便利路徑」工作、修法是 L3-L5 結構性對策（PR template / CI / pair）、不是「下次記得」</li>
<li><a href="/blog/report/search-engine-matching-mode-mismatch/" data-link-title="搜尋引擎的匹配模式跟使用者預期的對齊" data-link-desc="搜尋引擎的匹配模式（prefix / substring / fuzzy / semantic）各有不同。預設多半是 prefix（為了 index size）、但使用者被 Google 訓練成預期 substring。沒對齊 = silent 失敗：搜「pre」找不到 backpressure。本卡展開五種匹配模式、跟使用者意圖的對齊協議、五個合成策略。">#73 搜尋引擎的匹配模式跟使用者預期的對齊</a> — Checkpoint 1 列「使用者意圖完整集」要包含「使用者打字行為的預期」：工具預設 matching mode（prefix）跟使用者預期（substring，被 Google 訓練）對齊嗎？</li>
<li><a href="/blog/report/decision-presentation-options-recommendation/" data-link-title="決策呈現：選項 &#43; 推薦 &#43; 開放修改" data-link-desc="讓使用者做決定時、不要開放問「你覺得呢」 — 給選項、加適配性、標推薦、開放修改。開放問把「整理問題」的成本丟給使用者、推薦把判斷攤開供質疑、開放修改保留使用者的最終決定權。本卡是 #58 篩選三問、requirement-protocol 原則 1 的呈現面展開。">#74 決策呈現：選項 + 推薦 + 開放修改</a> — 不要開放問、給結構表 + 推薦、把整理問題的成本攤在 agent 而非 user</li>
<li><a href="/blog/report/main-strategy-plus-supplementary/" data-link-title="主策略 &#43; 補強策略：選擇不必互斥" data-link-desc="多策略並非「五選一」、可分層疊加：root-cause fix（解結構問題） &#43; UX 補強（解使用者感知）通常雙打比單選更穩。判準三條：解不同層 / 沒副作用衝突 / 增量成本可接受。把「策略選擇」預設成單選、會放掉互補可能、產生「結構修了但使用者體驗仍差」或「UX 蓋過去但結構還壞」。">#75 主策略 + 補強：選擇不必互斥</a> — 多策略可疊加（structural + UX）、預設「五選一」會放掉互補可能</li>
<li><a href="/blog/report/incremental-shipping-criteria/" data-link-title="分批 ship：低風險可見價值先行、結構性下輪" data-link-desc="「一次 ship 全部」的衝動 vs 「分批 ship」的設計：判準三軸（使用者可見性 / 風險暴露面 / 驗證需求）。低風險 &#43; 高可見 = 立刻 ship；高風險 &#43; 需驗證 = 下輪。對抗「完整才完整」的全做衝動、避免一次塞太多 review surface 拖延上線。">#76 分批 ship：可見性 + 風險 + 驗證三軸切分</a> — 「ship 順序 ≠ 重要程度」、低風險可見價值先 ship</li>
<li><a href="/blog/report/decide-later-as-valid-option/" data-link-title="「現在不決定」是合法選項：context 不足時延後決策" data-link-desc="被問到時不一定要立刻答 — 「先補 context、回頭再決」是合法選項、卻常被當「拖延」忽略。LLM / agent 預設「問了就要立刻答」是錯誤前提：使用者有權延後到 context 補齊、推薦時應主動標出「也可選『先 X 再回來決』」。本卡是 #58 篩選三問、#74 決策呈現的時間軸延伸。">#77 「現在不決定」是合法選項</a> — 決策表加「延後 + 條件」欄、區分逃避決策 vs 結構性延後</li>
<li><a href="/blog/report/retrospective-multi-select-default/" data-link-title="反省任務預設複選：互斥要證明、不互斥是預設" data-link-desc="反省 / retrospective / 改進方向類問題、預設應給「複選」而非「單選」 — 互斥需要明示證明、不互斥是預設。用 radio 限縮會讓使用者被迫排序、丟失多面向的同時性。本卡是 #74 決策呈現的反省場景特化、跟一般「執行類決策」（多半互斥）對立。">#78 反省任務預設複選</a> — 互斥要證明、不互斥是預設、反省題用 radio = 結構性 collapse</li>
<li><a href="/blog/report/decision-dialogue-dimensions/" data-link-title="決策對話的五個維度：保持完整選擇空間" data-link-desc="對話中的「決策」不是單一動作、是多維度選擇空間：呈現格式 / 策略疊加 / 批次邊界 / 時間軸 / 選項類型。預設多半 collapse 到最窄格（開放問 &#43; 單策略 &#43; 一次完成 &#43; 立刻決 &#43; 單選）、塞使用者進最少自由度的盒子。本卡是 #74-#78 的上層串連 — 五張卡各對應一個維度的鬆綁。">#79 決策對話的五維度</a> — meta-#74-#78、五個獨立維度的鬆綁、預設都選窄格 = 把使用者塞進最少自由度的盒子</li>
<li><a href="/blog/report/yes-no-binary-collapse/" data-link-title="Yes/No 二選是隱式 collapse：把多選空間壓成 1 bit" data-link-desc="「需要我繼續嗎？」「要做嗎？」「OK 嗎？」這類 yes/no 問句、看似最簡單其實是五維 collapse 的極致形態：1 bit 編碼掉「改 / 延後 / 疊加 / 分批 / 反問」五種合法回應。本卡是 #74-#79 系列在「最常見、最隱形」變種的特化卡。">#80 Yes/No 二選是隱式 collapse</a> — 「需要 X 嗎？」「OK 嗎？」最常見最隱形的 collapse、修法是翻成多選表</li>
<li><a href="/blog/report/cards-as-living-system-iteration/" data-link-title="卡片系統的迭代浮現：原子卡 → meta-卡 → reference 三層展開" data-link-desc="知識卡片系統不是一次寫成、是 dialogue → 原子卡 → meta-卡 → reference 的迭代浮現。每一輪迭代解決上一輪的 over-fit / under-fit、串連分散的卡片、抽出 meta-原則、最後沉澱成可直接套用的 reference 文件。本卡是 cards-skills 系統設計的 process-level 元原則。">#81 卡片系統的迭代浮現</a> — 本 skill 的 reference 是 spiral 浮現、不是線性寫成、process-level 元原則</li>
<li><a href="/blog/report/literal-interception-vs-behavioral-refinement/" data-link-title="字面攔截 vs 行為精煉：驗證手段跟錯誤層次的對齊" data-link-desc="驗證手段必須跟錯誤層次對齊：字面錯誤（typo / syntax / 缺欄位）用 hook / lint / CI 攔截；行為錯誤（思考偏差 / 判斷錯位 / collapse 反模式）用 multi-pass spiral 收斂。強行用 hook 蓋行為錯誤 = 給出 false confidence、反而比沒保護危險。本卡是 #72 結構性對策在「驗證粒度」維度的 ceiling — 不是所有錯誤都該被攔截。">#82 字面攔截 vs 行為精煉</a> — 驗證手段跟錯誤層次對齊、行為錯誤（如 collapse）靠 multi-pass spiral 收斂、不是「補一條 hook 規則」、本 skill 的 reference + dogfood examples + self-check 就是 multi-pass 設計</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.7.0 — Phase B1 結構升級：加第 4 pillar「Multi-pass Refinement」+ 第 7 原則、明示 multi-pass 在「需求協議」場景的展開、串連 #82 / #83 / #85
<strong>Version</strong>: 0.6.0 — 補 #82 (字面攔截 vs 行為精煉)：點出 hook 對行為錯誤無能為力、本 skill 的 reference + self-check + dogfood examples 就是 multi-pass 設計、不是「再補一條 hook 規則」
<strong>Version</strong>: 0.5.0 — 補 #80 (yes/no collapse) + #81 (卡片迭代浮現)、reference 加 dogfood examples 段（4 個 Bad/Good 對照）、#75 加 selector stacking 跨連 #46-#50
<strong>Version</strong>: 0.4.0 — 接入 #74-#79 決策協議系列：新增第 6 份 reference <code>decision-dialogue.md</code>（五維度：呈現 / 策略 / 批次 / 時間 / 選項類型）；觸發路由加 3 條入口（呈現決策 / 開放問 / 反省題）；相關抽象層原則段補 #74-#79
<strong>Version</strong>: 0.3.0 — 接入 #69-#73：相關抽象層原則段補 Test-First (#69)、URL state (#70)、tab order (#71)、外部觸發 meta (#72)、search 匹配模式 (#73)
<strong>Version</strong>: 0.2.0 — 接入 #55-#68 系列：clarifying-ambiguous-instructions 加第 5 類「篩選類」（呼應 #58）；觸發路由加篩選類入口；SKILL.md 加「相關抽象層原則」段（#42-45 + #67-68）
<strong>Version</strong>: 0.1.0 — 從 <code>content/report/</code> 50+ 篇事後檢討萃取「需求 → 實作對話協議」這條主軸；五份 references 對應「模糊指令 / 失敗轉折 / 成本與 checkpoint / 漸進驗證 / 工具切換」五個情境</p>
]]></content:encoded></item><item><title>Requirement Protocol — 需求確認到實作的對話協議</title><link>https://tarrragon.github.io/blog/skills/requirement-protocol/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/requirement-protocol/</guid><description>&lt;h2 id="這個資料夾是什麼">這個資料夾是什麼&lt;/h2>
&lt;p>&lt;code>requirement-protocol&lt;/code> 是一套對話協議 skill，原生位置在 &lt;a href="https://github.com/tarrragon/blog/tree/main/.claude/skills/requirement-protocol">&lt;code>.claude/skills/requirement-protocol/&lt;/code>&lt;/a> 供 Claude runtime 呼叫；這份是&lt;strong>同內容的文章版本&lt;/strong>，讓人類讀者也能直接在 blog 閱讀。&lt;/p>
&lt;p>把「使用者下指令 → 執行者實作」之間的溝通流程結構化、避免反覆失敗、避免做出使用者沒要的東西、避免在錯誤方向上累積沉沒成本。源頭是 &lt;a href="https://tarrragon.github.io/blog/report/" data-link-title="Report — 開發過程的事後檢討" data-link-desc="blog 開發過程中、把實際遇到的版型 / 整合 / 框架共處等情境、整理成『應該怎麼做、沒這樣做會有什麼麻煩』的事後檢討。每篇皆為正向指引、幫助下一輪同類任務跳過反覆試錯。">&lt;code>content/report/&lt;/code>&lt;/a> 累積的 50+ 篇事後檢討、由本 skill 的五份 reference 萃取對應五個情境的協議步驟。&lt;/p>
&lt;h2 id="閱讀順序">閱讀順序&lt;/h2>
&lt;h3 id="場景-1第一次接觸">場景 1：第一次接觸&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>1&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/requirement-protocol/skill/" data-link-title="Requirement Protocol — SKILL 入口" data-link-desc="從需求確認到實作的對話協議 SKILL 入口：四大支柱（含 multi-pass refinement）、七大原則速查、六份情境 reference（含篩選類指令、決策呈現五維度）的觸發路由 &amp;#43; 抽象層原則網路。">SKILL.md&lt;/a>&lt;/td>
 &lt;td>三大支柱 + 六大原則速查、觸發路由表&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2&lt;/td>
 &lt;td>依情境挑一份 reference（見下表）&lt;/td>
 &lt;td>把原則翻譯成可套用的協議步驟、模板與範例&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3&lt;/td>
 &lt;td>該 reference 結尾的 self-check checklist&lt;/td>
 &lt;td>自評有沒有按協議走&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="場景-2已熟悉協議想直接解決當前任務">場景 2：已熟悉協議、想直接解決當前任務&lt;/h3>
&lt;p>直接依觸發情境跳對應 reference：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>觸發情境&lt;/th>
 &lt;th>reference&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>收到模糊指令（含「對齊」「靠近」「隔離」「不要動」「分開」等）&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/requirement-protocol/clarifying-ambiguous-instructions/" data-link-title="Clarifying Ambiguous Instructions — 模糊指令澄清協議" data-link-desc="requirement-protocol reference：空間 / 相對位置 / 隔離 / 決定權 / 篩選五類模糊指令的澄清模板 &amp;#43; visible 三問判準 &amp;#43; 篩選三問。">clarifying-ambiguous-instructions&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>不確定某個決定該自決還是該先問使用者&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/requirement-protocol/clarifying-ambiguous-instructions/" data-link-title="Clarifying Ambiguous Instructions — 模糊指令澄清協議" data-link-desc="requirement-protocol reference：空間 / 相對位置 / 隔離 / 決定權 / 篩選五類模糊指令的澄清模板 &amp;#43; visible 三問判準 &amp;#43; 篩選三問。">clarifying-ambiguous-instructions&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>同方向失敗 ≥ 2 次、想再試一次更小心&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/requirement-protocol/failure-pivot-protocol/" data-link-title="Failure Pivot Protocol — 失敗 2 次的轉折協議" data-link-desc="requirement-protocol reference：同方向失敗 2 次的轉折協議 — 停下、驗證底層假設、換方向、對外回報模板。">failure-pivot-protocol&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>推理 + 視覺截圖溝通迴圈卡住、不知道該不該換工具&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/requirement-protocol/tool-switching-timing/" data-link-title="Tool Switching Timing — 推理 / DevTools / Playwright 的切換時機" data-link-desc="requirement-protocol reference：四種 debug 工具的 ROI 對照、切換訊號、playwright 三個位置（假設 / 行為 / 互動驗證）。">tool-switching-timing&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>客製需求要對抗多層（vendor CSS、framework、browser default）&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/requirement-protocol/cost-and-checkpoint/" data-link-title="Cost &amp;amp; Checkpoint — 覆寫成本告知與 revert checkpoint" data-link-desc="requirement-protocol reference：對抗多層的覆寫成本告知 &amp;#43; 「先還原 / 先重來」類退出指令的 checkpoint commit 處理。">cost-and-checkpoint&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>收到「先還原 / 先重來 / 換個方向」類指令&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/requirement-protocol/cost-and-checkpoint/" data-link-title="Cost &amp;amp; Checkpoint — 覆寫成本告知與 revert checkpoint" data-link-desc="requirement-protocol reference：對抗多層的覆寫成本告知 &amp;#43; 「先還原 / 先重來」類退出指令的 checkpoint commit 處理。">cost-and-checkpoint&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>開始 UI layout debug、不知道從哪一步起&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/requirement-protocol/progressive-verification/" data-link-title="Progressive Verification — 漸進驗證與最小必要範圍" data-link-desc="requirement-protocol reference：placeholder 漸進除錯 &amp;#43; measurement 完整性 &amp;#43; minimum necessary scope 三原則。">progressive-verification&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>設計 selector / MutationObserver root / JS 操作範圍&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/requirement-protocol/progressive-verification/" data-link-title="Progressive Verification — 漸進驗證與最小必要範圍" data-link-desc="requirement-protocol reference：placeholder 漸進除錯 &amp;#43; measurement 完整性 &amp;#43; minimum necessary scope 三原則。">progressive-verification&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每份 reference 自包含：讀任一份不需要回頭讀其他 reference。&lt;/p></description><content:encoded><![CDATA[<h2 id="這個資料夾是什麼">這個資料夾是什麼</h2>
<p><code>requirement-protocol</code> 是一套對話協議 skill，原生位置在 <a href="https://github.com/tarrragon/blog/tree/main/.claude/skills/requirement-protocol"><code>.claude/skills/requirement-protocol/</code></a> 供 Claude runtime 呼叫；這份是<strong>同內容的文章版本</strong>，讓人類讀者也能直接在 blog 閱讀。</p>
<p>把「使用者下指令 → 執行者實作」之間的溝通流程結構化、避免反覆失敗、避免做出使用者沒要的東西、避免在錯誤方向上累積沉沒成本。源頭是 <a href="/blog/report/" data-link-title="Report — 開發過程的事後檢討" data-link-desc="blog 開發過程中、把實際遇到的版型 / 整合 / 框架共處等情境、整理成『應該怎麼做、沒這樣做會有什麼麻煩』的事後檢討。每篇皆為正向指引、幫助下一輪同類任務跳過反覆試錯。"><code>content/report/</code></a> 累積的 50+ 篇事後檢討、由本 skill 的五份 reference 萃取對應五個情境的協議步驟。</p>
<h2 id="閱讀順序">閱讀順序</h2>
<h3 id="場景-1第一次接觸">場景 1：第一次接觸</h3>
<table>
  <thead>
      <tr>
          <th>順序</th>
          <th>檔案</th>
          <th>目的</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td><a href="/blog/skills/requirement-protocol/skill/" data-link-title="Requirement Protocol — SKILL 入口" data-link-desc="從需求確認到實作的對話協議 SKILL 入口：四大支柱（含 multi-pass refinement）、七大原則速查、六份情境 reference（含篩選類指令、決策呈現五維度）的觸發路由 &#43; 抽象層原則網路。">SKILL.md</a></td>
          <td>三大支柱 + 六大原則速查、觸發路由表</td>
      </tr>
      <tr>
          <td>2</td>
          <td>依情境挑一份 reference（見下表）</td>
          <td>把原則翻譯成可套用的協議步驟、模板與範例</td>
      </tr>
      <tr>
          <td>3</td>
          <td>該 reference 結尾的 self-check checklist</td>
          <td>自評有沒有按協議走</td>
      </tr>
  </tbody>
</table>
<h3 id="場景-2已熟悉協議想直接解決當前任務">場景 2：已熟悉協議、想直接解決當前任務</h3>
<p>直接依觸發情境跳對應 reference：</p>
<table>
  <thead>
      <tr>
          <th>觸發情境</th>
          <th>reference</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>收到模糊指令（含「對齊」「靠近」「隔離」「不要動」「分開」等）</td>
          <td><a href="/blog/skills/requirement-protocol/clarifying-ambiguous-instructions/" data-link-title="Clarifying Ambiguous Instructions — 模糊指令澄清協議" data-link-desc="requirement-protocol reference：空間 / 相對位置 / 隔離 / 決定權 / 篩選五類模糊指令的澄清模板 &#43; visible 三問判準 &#43; 篩選三問。">clarifying-ambiguous-instructions</a></td>
      </tr>
      <tr>
          <td>不確定某個決定該自決還是該先問使用者</td>
          <td><a href="/blog/skills/requirement-protocol/clarifying-ambiguous-instructions/" data-link-title="Clarifying Ambiguous Instructions — 模糊指令澄清協議" data-link-desc="requirement-protocol reference：空間 / 相對位置 / 隔離 / 決定權 / 篩選五類模糊指令的澄清模板 &#43; visible 三問判準 &#43; 篩選三問。">clarifying-ambiguous-instructions</a></td>
      </tr>
      <tr>
          <td>同方向失敗 ≥ 2 次、想再試一次更小心</td>
          <td><a href="/blog/skills/requirement-protocol/failure-pivot-protocol/" data-link-title="Failure Pivot Protocol — 失敗 2 次的轉折協議" data-link-desc="requirement-protocol reference：同方向失敗 2 次的轉折協議 — 停下、驗證底層假設、換方向、對外回報模板。">failure-pivot-protocol</a></td>
      </tr>
      <tr>
          <td>推理 + 視覺截圖溝通迴圈卡住、不知道該不該換工具</td>
          <td><a href="/blog/skills/requirement-protocol/tool-switching-timing/" data-link-title="Tool Switching Timing — 推理 / DevTools / Playwright 的切換時機" data-link-desc="requirement-protocol reference：四種 debug 工具的 ROI 對照、切換訊號、playwright 三個位置（假設 / 行為 / 互動驗證）。">tool-switching-timing</a></td>
      </tr>
      <tr>
          <td>客製需求要對抗多層（vendor CSS、framework、browser default）</td>
          <td><a href="/blog/skills/requirement-protocol/cost-and-checkpoint/" data-link-title="Cost &amp; Checkpoint — 覆寫成本告知與 revert checkpoint" data-link-desc="requirement-protocol reference：對抗多層的覆寫成本告知 &#43; 「先還原 / 先重來」類退出指令的 checkpoint commit 處理。">cost-and-checkpoint</a></td>
      </tr>
      <tr>
          <td>收到「先還原 / 先重來 / 換個方向」類指令</td>
          <td><a href="/blog/skills/requirement-protocol/cost-and-checkpoint/" data-link-title="Cost &amp; Checkpoint — 覆寫成本告知與 revert checkpoint" data-link-desc="requirement-protocol reference：對抗多層的覆寫成本告知 &#43; 「先還原 / 先重來」類退出指令的 checkpoint commit 處理。">cost-and-checkpoint</a></td>
      </tr>
      <tr>
          <td>開始 UI layout debug、不知道從哪一步起</td>
          <td><a href="/blog/skills/requirement-protocol/progressive-verification/" data-link-title="Progressive Verification — 漸進驗證與最小必要範圍" data-link-desc="requirement-protocol reference：placeholder 漸進除錯 &#43; measurement 完整性 &#43; minimum necessary scope 三原則。">progressive-verification</a></td>
      </tr>
      <tr>
          <td>設計 selector / MutationObserver root / JS 操作範圍</td>
          <td><a href="/blog/skills/requirement-protocol/progressive-verification/" data-link-title="Progressive Verification — 漸進驗證與最小必要範圍" data-link-desc="requirement-protocol reference：placeholder 漸進除錯 &#43; measurement 完整性 &#43; minimum necessary scope 三原則。">progressive-verification</a></td>
      </tr>
  </tbody>
</table>
<p>每份 reference 自包含：讀任一份不需要回頭讀其他 reference。</p>
<h2 id="與-blog-專案其他資料的關係">與 blog 專案其他資料的關係</h2>
<table>
  <thead>
      <tr>
          <th>位置</th>
          <th>角色</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>.claude/skills/requirement-protocol/</code></td>
          <td>實際 skill — Claude runtime 呼叫的檔案來源</td>
      </tr>
      <tr>
          <td><code>content/skills/requirement-protocol/</code>（本處）</td>
          <td>文章版本 — 人類讀者在 blog 閱讀</td>
      </tr>
      <tr>
          <td><a href="/blog/report/" data-link-title="Report — 開發過程的事後檢討" data-link-desc="blog 開發過程中、把實際遇到的版型 / 整合 / 框架共處等情境、整理成『應該怎麼做、沒這樣做會有什麼麻煩』的事後檢討。每篇皆為正向指引、幫助下一輪同類任務跳過反覆試錯。"><code>content/report/</code></a></td>
          <td>50+ 篇事後檢討、本 skill 的素材來源；reference 結尾連回對應篇</td>
      </tr>
      <tr>
          <td><code>.claude/skills/compositional-writing/</code></td>
          <td>寫作方法論 skill — 本 skill 的 references 撰寫品質依此規範</td>
      </tr>
  </tbody>
</table>
<h2 id="last-updated">Last Updated</h2>
<p>2026-04-26 — 初版：v0.1.0 同步、五份 references 對應「模糊指令 / 失敗轉折 / 成本與 checkpoint / 漸進驗證 / 工具切換」五個情境。</p>
]]></content:encoded></item><item><title>Tool Switching Timing — 推理 / DevTools / Playwright 的切換時機</title><link>https://tarrragon.github.io/blog/skills/requirement-protocol/tool-switching-timing/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/requirement-protocol/tool-switching-timing/</guid><description>&lt;p>何時從靜態推理切換到量測工具、何時從 DevTools 升級到 Playwright、何時把 debug 過程寫成測試。&lt;/p>
&lt;p>適用：CSS / DOM debug、layout 卡關、不確定該用哪個工具。
不適用：純邏輯 bug（這時 logging / debugger 比 layout 工具有用）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋四種工具的 ROI 對照、切換時機、最低門檻入口。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="何時參閱本文件">何時參閱本文件&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>訊號&lt;/th>
 &lt;th>該做的第一件事&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>推理 ≥ 2 次失敗&lt;/td>
 &lt;td>切到 playwright &lt;code>browser_evaluate&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>視覺截圖溝通迴圈卡住、雙方對「哪裡不對」沒共識&lt;/td>
 &lt;td>切到 playwright + 量化資料（rect / style）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Layout 在某些狀態下錯、其他狀態下對&lt;/td>
 &lt;td>切到 playwright、量不同狀態的 bounding rect&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>改 CSS 不生效、specificity 看起來對&lt;/td>
 &lt;td>切到 playwright、量 computed style&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>同一個版型 bug 第 2 次出現&lt;/td>
 &lt;td>切到「寫成 playwright 測試」固化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>一次性確認 DOM 結構、不會重複查&lt;/td>
 &lt;td>用 DevTools 即可、不需要起 server&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼工具切換要早不該等到推理徹底失敗">為什麼工具切換要早、不該等到推理徹底失敗&lt;/h2>
&lt;p>CSS 行為由「規則 + DOM tree + 樣式繼承 + 框架渲染」四個變數共同決定。&lt;strong>靜態推理只能基於假設的 DOM tree&lt;/strong> — 假設錯了、推理就錯。視覺截圖只能傳達「結果是什麼」、無法傳達「為什麼」。&lt;/p>
&lt;p>Playwright 的 &lt;code>browser_evaluate&lt;/code> 直接執行 JS 在 live page、返回真實的 DOM tree、computed style、bounding rect — &lt;strong>把四個變數全部變成已知&lt;/strong>。&lt;/p>
&lt;p>&lt;strong>門檻在第 2 次&lt;/strong>：第 1 次推理快（假設正確時一次到位）；第 2 次推理失敗 → 假設可能錯 → 繼續推理會在錯誤假設上累積。Playwright 起步成本中、但後續穩定。&lt;/p>
&lt;hr>
&lt;h2 id="四種工具的-roi-對照">四種工具的 ROI 對照&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>方法&lt;/th>
 &lt;th>取得資訊量&lt;/th>
 &lt;th>起步成本&lt;/th>
 &lt;th>重複成本&lt;/th>
 &lt;th>可寫成測試&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>靜態 CSS 推理&lt;/td>
 &lt;td>低 — 全是假設&lt;/td>
 &lt;td>0&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;td>否&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>瀏覽器 DevTools&lt;/td>
 &lt;td>高 — DOM + computed&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>否&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Playwright &lt;code>browser_evaluate&lt;/code>&lt;/td>
 &lt;td>最高 — 程式化任意查詢&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>是 — 同樣 query 可寫測試&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>選擇順序&lt;/strong>：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>情境&lt;/th>
 &lt;th>工具&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>第 1 次推理（簡單修改、假設正確機率高）&lt;/td>
 &lt;td>靜態推理 + 截圖&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>一次性確認、不重複查&lt;/td>
 &lt;td>DevTools&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>推理 ≥ 2 次失敗 / 反覆 debug&lt;/td>
 &lt;td>Playwright &lt;code>browser_evaluate&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>同個版型 bug 第 2 次以上&lt;/td>
 &lt;td>Playwright 測試固化&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="playwright-在開發循環的三個位置">Playwright 在開發循環的三個位置&lt;/h2>
&lt;h3 id="位置-1假設驗證寫-css-規則前">位置 1：假設驗證（寫 CSS 規則前）&lt;/h3>
&lt;p>確認 DOM 結構符合假設。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&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="kr">const&lt;/span> &lt;span class="nx">drawer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;.pagefind-ui__drawer&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[];&lt;/span> &lt;span class="kd">let&lt;/span> &lt;span class="nx">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">drawer&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">n&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">n&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">body&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="nx">chain&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">n&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">tagName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">.&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">n&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">className&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="nx">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">n&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parentElement&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">chain&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>返回值對照假設、發現 &lt;code>drawer&lt;/code> 是 &lt;code>form&lt;/code> 的 child（不是 sibling）→ grid-row 控制無效、改方向。&lt;/p></description><content:encoded><![CDATA[<p>何時從靜態推理切換到量測工具、何時從 DevTools 升級到 Playwright、何時把 debug 過程寫成測試。</p>
<p>適用：CSS / DOM debug、layout 卡關、不確定該用哪個工具。
不適用：純邏輯 bug（這時 logging / debugger 比 layout 工具有用）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋四種工具的 ROI 對照、切換時機、最低門檻入口。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>推理 ≥ 2 次失敗</td>
          <td>切到 playwright <code>browser_evaluate</code></td>
      </tr>
      <tr>
          <td>視覺截圖溝通迴圈卡住、雙方對「哪裡不對」沒共識</td>
          <td>切到 playwright + 量化資料（rect / style）</td>
      </tr>
      <tr>
          <td>Layout 在某些狀態下錯、其他狀態下對</td>
          <td>切到 playwright、量不同狀態的 bounding rect</td>
      </tr>
      <tr>
          <td>改 CSS 不生效、specificity 看起來對</td>
          <td>切到 playwright、量 computed style</td>
      </tr>
      <tr>
          <td>同一個版型 bug 第 2 次出現</td>
          <td>切到「寫成 playwright 測試」固化</td>
      </tr>
      <tr>
          <td>一次性確認 DOM 結構、不會重複查</td>
          <td>用 DevTools 即可、不需要起 server</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼工具切換要早不該等到推理徹底失敗">為什麼工具切換要早、不該等到推理徹底失敗</h2>
<p>CSS 行為由「規則 + DOM tree + 樣式繼承 + 框架渲染」四個變數共同決定。<strong>靜態推理只能基於假設的 DOM tree</strong> — 假設錯了、推理就錯。視覺截圖只能傳達「結果是什麼」、無法傳達「為什麼」。</p>
<p>Playwright 的 <code>browser_evaluate</code> 直接執行 JS 在 live page、返回真實的 DOM tree、computed style、bounding rect — <strong>把四個變數全部變成已知</strong>。</p>
<p><strong>門檻在第 2 次</strong>：第 1 次推理快（假設正確時一次到位）；第 2 次推理失敗 → 假設可能錯 → 繼續推理會在錯誤假設上累積。Playwright 起步成本中、但後續穩定。</p>
<hr>
<h2 id="四種工具的-roi-對照">四種工具的 ROI 對照</h2>
<table>
  <thead>
      <tr>
          <th>方法</th>
          <th>取得資訊量</th>
          <th>起步成本</th>
          <th>重複成本</th>
          <th>可寫成測試</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>靜態 CSS 推理</td>
          <td>低 — 全是假設</td>
          <td>0</td>
          <td>高</td>
          <td>否</td>
      </tr>
      <tr>
          <td>視覺截圖溝通</td>
          <td>中 — 只有結果</td>
          <td>低</td>
          <td>中</td>
          <td>否</td>
      </tr>
      <tr>
          <td>瀏覽器 DevTools</td>
          <td>高 — DOM + computed</td>
          <td>低</td>
          <td>中</td>
          <td>否</td>
      </tr>
      <tr>
          <td>Playwright <code>browser_evaluate</code></td>
          <td>最高 — 程式化任意查詢</td>
          <td>中</td>
          <td>低</td>
          <td>是 — 同樣 query 可寫測試</td>
      </tr>
  </tbody>
</table>
<p><strong>選擇順序</strong>：</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>工具</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>第 1 次推理（簡單修改、假設正確機率高）</td>
          <td>靜態推理 + 截圖</td>
      </tr>
      <tr>
          <td>一次性確認、不重複查</td>
          <td>DevTools</td>
      </tr>
      <tr>
          <td>推理 ≥ 2 次失敗 / 反覆 debug</td>
          <td>Playwright <code>browser_evaluate</code></td>
      </tr>
      <tr>
          <td>同個版型 bug 第 2 次以上</td>
          <td>Playwright 測試固化</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="playwright-在開發循環的三個位置">Playwright 在開發循環的三個位置</h2>
<h3 id="位置-1假設驗證寫-css-規則前">位置 1：假設驗證（寫 CSS 規則前）</h3>
<p>確認 DOM 結構符合假設。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kr">const</span> <span class="nx">drawer</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.pagefind-ui__drawer&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="kd">let</span> <span class="nx">chain</span> <span class="o">=</span> <span class="p">[];</span> <span class="kd">let</span> <span class="nx">n</span> <span class="o">=</span> <span class="nx">drawer</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="k">while</span> <span class="p">(</span><span class="nx">n</span> <span class="o">&amp;&amp;</span> <span class="nx">n</span> <span class="o">!==</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nx">chain</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">n</span><span class="p">.</span><span class="nx">tagName</span><span class="si">}</span><span class="sb">.</span><span class="si">${</span><span class="nx">n</span><span class="p">.</span><span class="nx">className</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nx">n</span> <span class="o">=</span> <span class="nx">n</span><span class="p">.</span><span class="nx">parentElement</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="k">return</span> <span class="nx">chain</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>返回值對照假設、發現 <code>drawer</code> 是 <code>form</code> 的 child（不是 sibling）→ grid-row 控制無效、改方向。</p>
<h3 id="位置-2行為驗證layout-規則寫完後">位置 2：行為驗證（layout 規則寫完後）</h3>
<p>驗證實際 layout 結果。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nx">rect</span><span class="o">:</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.target&#39;</span><span class="p">).</span><span class="nx">getBoundingClientRect</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">computedTop</span><span class="o">:</span> <span class="nx">getComputedStyle</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.target&#39;</span><span class="p">)).</span><span class="nx">top</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">computedDisplay</span><span class="o">:</span> <span class="nx">getComputedStyle</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.target&#39;</span><span class="p">)).</span><span class="nx">display</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><h3 id="位置-3互動驗證使用者操作後的狀態">位置 3：互動驗證（使用者操作後的狀態）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kr">const</span> <span class="nx">input</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.search-input&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">input</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="s1">&#39;pre&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">input</span><span class="p">.</span><span class="nx">dispatchEvent</span><span class="p">(</span><span class="k">new</span> <span class="nx">Event</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">bubbles</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}));</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="kr">await</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">r</span><span class="p">,</span> <span class="mi">1000</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="k">return</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">&#39;.result&#39;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">el</span> <span class="p">=&gt;</span> <span class="nx">getComputedStyle</span><span class="p">(</span><span class="nx">el</span><span class="p">).</span><span class="nx">display</span> <span class="o">!==</span> <span class="s1">&#39;none&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">el</span> <span class="p">=&gt;</span> <span class="nx">el</span><span class="p">.</span><span class="nx">textContent</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">50</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="第-2-次同個-bug--寫成測試固化">第 2 次同個 bug → 寫成測試固化</h2>
<p>第 1 次 debug 完、bug 修好。第 2 次同個版型問題（不同 commit / 不同 viewport）再出現 → <strong>debug 完後把 query 寫成 playwright 測試</strong>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">test</span><span class="p">(</span><span class="s1">&#39;search scope is between form and results&#39;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s1">&#39;/search/?q=pre&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="kr">const</span> <span class="nx">formRect</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">&#39;.pagefind-ui__form&#39;</span><span class="p">).</span><span class="nx">boundingBox</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="kr">const</span> <span class="nx">scopeRect</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">&#39;.scope-toggle&#39;</span><span class="p">).</span><span class="nx">boundingBox</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="kr">const</span> <span class="nx">resultsRect</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">&#39;.results&#39;</span><span class="p">).</span><span class="nx">boundingBox</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nx">expect</span><span class="p">(</span><span class="nx">scopeRect</span><span class="p">.</span><span class="nx">y</span><span class="p">).</span><span class="nx">toBeGreaterThan</span><span class="p">(</span><span class="nx">formRect</span><span class="p">.</span><span class="nx">y</span> <span class="o">+</span> <span class="nx">formRect</span><span class="p">.</span><span class="nx">height</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="nx">expect</span><span class="p">(</span><span class="nx">resultsRect</span><span class="p">.</span><span class="nx">y</span><span class="p">).</span><span class="nx">toBeGreaterThan</span><span class="p">(</span><span class="nx">scopeRect</span><span class="p">.</span><span class="nx">y</span> <span class="o">+</span> <span class="nx">scopeRect</span><span class="p">.</span><span class="nx">height</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>未來 layout 改動觸發 regression、CI 立刻發現、不需要再人工 debug。</p>
<hr>
<h2 id="playwright-引入的最低門檻">Playwright 引入的最低門檻</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 起本地 server（任何方式）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">python3 -m http.server <span class="m">8000</span> --directory public
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 或 hugo server</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">hugo server</span></span></code></pre></div><p>Playwright MCP 提供的核心工具：</p>
<ul>
<li><code>browser_navigate(url)</code> — 開頁</li>
<li><code>browser_evaluate(fn)</code> — 執行 JS 拿結果</li>
<li><code>browser_take_screenshot()</code> — 截圖</li>
<li><code>browser_snapshot()</code> — accessibility tree</li>
</ul>
<p>寫一個 evaluate fn ≈ 30 行 JS。比反覆推理快得多。</p>
<hr>
<h2 id="主動切換訊號不要等使用者打斷">主動切換訊號（不要等使用者打斷）</h2>
<p>當以下任一觸發、執行者要主動提：「我推理 2 次失敗了、我們起 server、用 playwright 量 live DOM 確認假設」。<strong>不要等到第 5 次才切</strong>。</p>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>對外回報句式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>同方向 CSS 規則改了 2 次都不生效</td>
          <td>「我假設 X 是 Y、playwright 一查就知道、要起 server？」</td>
      </tr>
      <tr>
          <td>截圖看起來對 / 不對、但雙方對「為什麼」沒共識</td>
          <td>「用 playwright 量 bounding rect、量化比較好？」</td>
      </tr>
      <tr>
          <td>改完 JS 後元素被還原</td>
          <td>「playwright 量 framework 重渲染週期、確認時機」</td>
      </tr>
      <tr>
          <td>Layout 在某些 state 下錯、其他對</td>
          <td>「我用 playwright 各 state 量一次 rect、做對照」</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1css-不生效">範例 1：CSS 不生效</h3>
<p><strong>錯</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln">1</span><span class="cl"><span class="c">/* 改了 3 次 specificity、還是沒生效 */</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">.</span><span class="nc">target</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="kc">red</span><span class="p">;</span> <span class="p">}</span>                    <span class="c">/* 失敗 */</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">.</span><span class="nc">parent</span> <span class="p">.</span><span class="nc">target</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="kc">red</span><span class="p">;</span> <span class="p">}</span>            <span class="c">/* 失敗 */</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">.</span><span class="nc">parent</span> <span class="p">.</span><span class="nc">container</span> <span class="p">.</span><span class="nc">target</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="kc">red</span><span class="p">;</span> <span class="p">}</span> <span class="c">/* 失敗 */</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">.</span><span class="nc">parent</span> <span class="p">.</span><span class="nc">container</span> <span class="p">.</span><span class="nc">target</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="kc">red</span> <span class="cp">!important</span><span class="p">;</span> <span class="p">}</span> <span class="c">/* 失敗 */</span></span></span></code></pre></div><p><strong>對</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">.</span><span class="nc">target</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="kc">red</span><span class="p">;</span> <span class="p">}</span></span></span></code></pre></div><p>第 2 次失敗 → 切 playwright：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">getComputedStyle</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.target&#39;</span><span class="p">)).</span><span class="nx">color</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// 返回 &#34;rgb(0, 0, 255)&#34; — 不是我寫的紅色
</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kr">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.target&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">return</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">getMatchedCSSRules</span><span class="o">?</span><span class="p">.(</span><span class="nx">el</span><span class="p">)</span> <span class="o">||</span> <span class="p">[])</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nx">cssText</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">// 看到 vendor 的 .pagefind .target { color: blue !important } 在贏
</span></span></span></code></pre></div><p>→ 換方向：用 CSS Layers 把 vendor CSS 包進 layer、自家 unlayered 自動贏。</p>
<h3 id="範例-2layout-在-mobile-viewport-錯">範例 2：Layout 在 mobile viewport 錯</h3>
<p><strong>錯</strong>：</p>
<p>反覆推理 + 在 DevTools 切 viewport 視覺確認 → 改 → 失敗 → 改 → 失敗。</p>
<p><strong>對</strong>：</p>
<p>第 2 次推理失敗、切 playwright：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">setViewportSize</span><span class="p">({</span> <span class="nx">width</span><span class="o">:</span> <span class="mi">375</span><span class="p">,</span> <span class="nx">height</span><span class="o">:</span> <span class="mi">667</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">h1</span><span class="o">:</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;h1&#39;</span><span class="p">).</span><span class="nx">getBoundingClientRect</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nx">form</span><span class="o">:</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;form&#39;</span><span class="p">).</span><span class="nx">getBoundingClientRect</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nx">scope</span><span class="o">:</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.scope&#39;</span><span class="p">).</span><span class="nx">getBoundingClientRect</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>量化資料 → 立刻看到「scope 的 top 比 form 的 bottom 小 12px」→ overlap → 改 form margin-bottom。</p>
<hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>debug 卡關時：</p>
<ul>
<li><input disabled="" type="checkbox"> 我推理失敗幾次了？≥ 2 次 → 該切換工具</li>
<li><input disabled="" type="checkbox"> 我能說出「假設是什麼、用什麼工具能驗證」嗎？</li>
<li><input disabled="" type="checkbox"> 切到 playwright 之前、有沒有試圖用更努力的推理多撐一次？（如果有 → 停）</li>
<li><input disabled="" type="checkbox"> 第 2 次同個版型 bug 出現時、有沒有寫成測試固化？</li>
<li><input disabled="" type="checkbox"> 對外回報切換工具的提案、有沒有寫得具體（要起哪個 server、量什麼）？</li>
</ul>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code>）：</p>
<ul>
<li><a href="/blog/report/playwright-early-in-loop/" data-link-title="在開發循環裡早一點用 playwright 看真實結果" data-link-desc="靜態 CSS 推理跟視覺截圖溝通有極限 — 當行為與預期不符 ≥ 2 次，stop 推理、改用 playwright browser_evaluate 直接讀 live DOM。本文說明工具引入時機。">playwright-early-in-loop</a> — 在開發循環裡早一點用 playwright 看真實結果</li>
<li><a href="/blog/report/verification-method-timing/" data-link-title="驗證方法的選擇時機" data-link-desc="靜態 CSS 推理 ≥ 2 次失敗就主動提『啟個 server、用 playwright 看 live DOM 比較快』、不要繼續猜。本文展開驗證工具的引入時機。">verification-method-timing</a> — 驗證方法的選擇時機</li>
<li><a href="/blog/report/layout-tests-with-playwright/" data-link-title="用前端測試把排版問題自動化" data-link-desc="排版問題傳統靠人眼檢查、容易遺漏邊界 case。當一個版型被 debug 兩次以上、就值得寫成 playwright 測試把規範固定下來。本文展開測試替代手動檢查的時機。">layout-tests-with-playwright</a> — 用前端測試把排版問題自動化</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.1.0</p>
]]></content:encoded></item></channel></rss>