<?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>Skills on Tarragon</title><link>https://tarrragon.github.io/blog/tags/skills/</link><description>Recent content in Skills on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 20 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/skills/index.xml" rel="self" type="application/rss+xml"/><item><title>Source to Teaching Analysis — 外部分析材料轉教學文章</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/source-to-teaching-analysis/</link><pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/source-to-teaching-analysis/</guid><description>&lt;p>本 reference 為「把外部分析文章或高密度研究材料，轉成教學型分析文章」情境。適用於分析師文章、投資人備忘錄、產業評論、研究摘要、AI 轉寫稿。目標是把 source 轉成讀者可重用的判讀框架，不是只做摘要、翻譯或語氣改寫。&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>要把外部分析師文章改寫成自己的教學文章&lt;/td>
 &lt;td>跑完整 source-to-teaching pass&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AI 改寫稿讀起來流暢、但像原文摘要&lt;/td>
 &lt;td>跑 deliverable pass&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>文章術語密度像寫給同業、目標讀者跟不上&lt;/td>
 &lt;td>跑 reader-level pass&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>來源文章的觀點與本文結論混在一起&lt;/td>
 &lt;td>跑 source layering pass&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>文章已套決策框架、但正文像分析過程記錄&lt;/td>
 &lt;td>跑 title promise 與 teaching narrative pass&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>不適用：純翻譯逐句校稿、學術論文正式摘要、內部 analyst-to-analyst 報告。這些產物可以保留原文的 reader contract 或 process metadata。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼外部分析改寫需要獨立指引">為什麼外部分析改寫需要獨立指引&lt;/h2>
&lt;p>外部分析文章同時帶有三種東西：事實材料、原作者 frame、原作者 reader contract。AI 或人類若只做風格轉換，會把三者一起搬進新文章。結果是文字符合目標風格，但結論仍沿用原作者視角，讀者仍需要原領域背景，文章也缺少可遷移的教學框架。&lt;/p>
&lt;p>教學型分析文章要做的是「source transformation」：保留可驗證事實，辨識原作者判讀，重建適合目標讀者的推導，最後交付可重用的判斷問題與預警訊號。&lt;/p>
&lt;hr>
&lt;h2 id="五個-pass">五個 Pass&lt;/h2>
&lt;h3 id="pass-1source-layering">Pass 1：Source Layering&lt;/h3>
&lt;p>Source layering 是把材料拆成事實、原作者判讀、本文推導。每句材料進正文前先問：&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>這件事有公開來源可驗證嗎？&lt;/td>
 &lt;td>事實&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>這是不是原作者或市場敘事的解釋？&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;/p>
&lt;ul>
&lt;li>事實放在背景或事件段。&lt;/li>
&lt;li>原作者判讀只當 hypothesis prior，不直接當本文結論。&lt;/li>
&lt;li>本文推導要明確標成本文整理出的框架或判斷問題。&lt;/li>
&lt;/ul>
&lt;p>反例：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">這代表基礎設施廠商正在垂直整合 data pipeline。&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>問題：若「垂直整合」是本文推導，這句把推導寫成事實。&lt;/p>
&lt;p>正例：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">本文把這個收購視為「基礎設施廠商往 data pipeline 延伸」的訊號。這是本文判讀，不是事件本身。&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="pass-2reader-level">Pass 2：Reader Level&lt;/h3>
&lt;p>Reader-level pass 是把原文 reader contract 改成目標讀者 contract。先辨識原文寫給誰，再決定是否降一級。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>原文類型&lt;/th>
 &lt;th>常見 reader contract&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>預設懂市場 shorthand&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>檢查是否符合本站段落責任&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>檢查法：&lt;/p>
&lt;ul>
&lt;li>三句內連續三個以上跨領域術語，拆段或補卡。&lt;/li>
&lt;li>一句話跨三個以上因果步，拆成多段。&lt;/li>
&lt;li>讀者能背結論但說不出推導，補機制、公式、數字或例子。&lt;/li>
&lt;/ul>
&lt;h3 id="pass-3internal-analysis">Pass 3：Internal Analysis&lt;/h3>
&lt;p>Internal analysis 是作者背後的 hypothesis 探索，不等於文章章節。可以用 WRAP 或其他決策框架做內部檢查：&lt;/p>
&lt;ul>
&lt;li>要回答什麼問題？&lt;/li>
&lt;li>手上 evidence 夠不夠？&lt;/li>
&lt;li>有哪些本質不同的合理解釋？&lt;/li>
&lt;li>每個解釋的 evidence weight 是什麼？&lt;/li>
&lt;li>哪些觀察會推翻本文判讀？&lt;/li>
&lt;/ul>
&lt;p>這些問題要做完，但不直接當正文標題。正文標題描述讀者會學到什麼，不描述作者正在執行哪個分析步驟。&lt;/p>
&lt;h3 id="pass-4title-promise">Pass 4：Title Promise&lt;/h3>
&lt;p>Title promise 是標題對讀者做的合約。跑完內部分析後，列出文章每段是否服務標題承諾。&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>某段超過 20% 篇幅但不在標題承諾內&lt;/td>
 &lt;td>壓縮成背景句、移到別篇、或刪除&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>來源 prior 比正文機制更長&lt;/td>
 &lt;td>縮減 prior，讓主體回到本文承諾&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>標題承諾的是框架、正文卻在解釋作者動機&lt;/td>
 &lt;td>改寫主體或重命名標題&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Title promise pass 保護讀者不被作者的分析過程帶離主題。&lt;/p>
&lt;h3 id="pass-5deliverable">Pass 5：Deliverable&lt;/h3>
&lt;p>Deliverable pass 確認文章交付的是可遷移框架，而不是原文摘要。結尾至少回答：&lt;/p>
&lt;ul>
&lt;li>下次遇到同類事件要看哪些訊號？&lt;/li>
&lt;li>哪些機制若成立，本文判讀才成立？&lt;/li>
&lt;li>哪些觀察會削弱或推翻本文判讀？&lt;/li>
&lt;li>讀者下一步該看哪個概念、卡片或案例？&lt;/li>
&lt;/ul>
&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">事件本身 → 結構性機制 → 長期影響 → 預警訊號 → 可遷移框架&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這是責任清單，各文章可以調整順序。不同文章可以調整順序，但不能缺少可遷移框架與失效條件。&lt;/p>
&lt;hr>
&lt;h2 id="自檢清單">自檢清單&lt;/h2>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 每個來源句子都能標成事實、原作者判讀、本文推導之一。&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 原作者 frame 沒有被寫成事件事實。&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 目標讀者不需要原文 reader contract 也能理解主體推導。&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 三句內沒有未解釋的連續跨領域術語。&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 內部分析框架沒有外露成正文標題。&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 正文段落都服務 title promise。&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 結尾提供可遷移框架、預警訊號與下一步路由。&lt;/li>
&lt;/ul>
&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>只要求 AI「改成我們的風格」&lt;/td>
 &lt;td>改成「抽出可遷移判讀框架」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>把原作者分類直接當本文標題&lt;/td>
 &lt;td>標出這是原作者 frame 或本文 frame&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>引用機構名但沒有具體來源&lt;/td>
 &lt;td>刪具名 attribution 或重新查證&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>章節標題是分析 process&lt;/td>
 &lt;td>改成教學標題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>文章結尾只有事件結論&lt;/td>
 &lt;td>補下次遇到同類事件的判斷問題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>讀者需要懂原領域 shorthand 才懂&lt;/td>
 &lt;td>降術語密度，補 bridge sentence&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>核心原則&lt;/strong>：外部分析文章改寫的核心是 source transformation。保留事實、分離原作者判讀、重建目標讀者可理解的推導，最後交付可遷移框架。&lt;/p></description><content:encoded><![CDATA[<p>本 reference 為「把外部分析文章或高密度研究材料，轉成教學型分析文章」情境。適用於分析師文章、投資人備忘錄、產業評論、研究摘要、AI 轉寫稿。目標是把 source 轉成讀者可重用的判讀框架，不是只做摘要、翻譯或語氣改寫。</p>
<blockquote>
<p><strong>自包含聲明</strong>：本文件不依賴其他 reference。讀完本文件即可獨立執行外部分析材料到教學文章的轉換。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>觸發情境</th>
          <th>動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>要把外部分析師文章改寫成自己的教學文章</td>
          <td>跑完整 source-to-teaching pass</td>
      </tr>
      <tr>
          <td>AI 改寫稿讀起來流暢、但像原文摘要</td>
          <td>跑 deliverable pass</td>
      </tr>
      <tr>
          <td>文章術語密度像寫給同業、目標讀者跟不上</td>
          <td>跑 reader-level pass</td>
      </tr>
      <tr>
          <td>來源文章的觀點與本文結論混在一起</td>
          <td>跑 source layering pass</td>
      </tr>
      <tr>
          <td>文章已套決策框架、但正文像分析過程記錄</td>
          <td>跑 title promise 與 teaching narrative pass</td>
      </tr>
  </tbody>
</table>
<p>不適用：純翻譯逐句校稿、學術論文正式摘要、內部 analyst-to-analyst 報告。這些產物可以保留原文的 reader contract 或 process metadata。</p>
<hr>
<h2 id="為什麼外部分析改寫需要獨立指引">為什麼外部分析改寫需要獨立指引</h2>
<p>外部分析文章同時帶有三種東西：事實材料、原作者 frame、原作者 reader contract。AI 或人類若只做風格轉換，會把三者一起搬進新文章。結果是文字符合目標風格，但結論仍沿用原作者視角，讀者仍需要原領域背景，文章也缺少可遷移的教學框架。</p>
<p>教學型分析文章要做的是「source transformation」：保留可驗證事實，辨識原作者判讀，重建適合目標讀者的推導，最後交付可重用的判斷問題與預警訊號。</p>
<hr>
<h2 id="五個-pass">五個 Pass</h2>
<h3 id="pass-1source-layering">Pass 1：Source Layering</h3>
<p>Source layering 是把材料拆成事實、原作者判讀、本文推導。每句材料進正文前先問：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>層級</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>這件事有公開來源可驗證嗎？</td>
          <td>事實</td>
      </tr>
      <tr>
          <td>這是不是原作者或市場敘事的解釋？</td>
          <td>原作者判讀</td>
      </tr>
      <tr>
          <td>這是不是本文從多個材料合成出的框架？</td>
          <td>本文推導</td>
      </tr>
  </tbody>
</table>
<p>寫法規則：</p>
<ul>
<li>事實放在背景或事件段。</li>
<li>原作者判讀只當 hypothesis prior，不直接當本文結論。</li>
<li>本文推導要明確標成本文整理出的框架或判斷問題。</li>
</ul>
<p>反例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">這代表基礎設施廠商正在垂直整合 data pipeline。</span></span></code></pre></div><p>問題：若「垂直整合」是本文推導，這句把推導寫成事實。</p>
<p>正例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">本文把這個收購視為「基礎設施廠商往 data pipeline 延伸」的訊號。這是本文判讀，不是事件本身。</span></span></code></pre></div><h3 id="pass-2reader-level">Pass 2：Reader Level</h3>
<p>Reader-level pass 是把原文 reader contract 改成目標讀者 contract。先辨識原文寫給誰，再決定是否降一級。</p>
<table>
  <thead>
      <tr>
          <th>原文類型</th>
          <th>常見 reader contract</th>
          <th>轉換策略</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>大眾新聞</td>
          <td>只需要知道事件</td>
          <td>補機制與可遷移框架</td>
      </tr>
      <tr>
          <td>投資人備忘錄</td>
          <td>預設懂市場 shorthand</td>
          <td>降術語密度、補因果鏈</td>
      </tr>
      <tr>
          <td>產業內部文</td>
          <td>預設懂供應鏈與競爭格局</td>
          <td>補背景角色與資料形狀</td>
      </tr>
      <tr>
          <td>教材型文章</td>
          <td>已經有教學推導</td>
          <td>檢查是否符合本站段落責任</td>
      </tr>
  </tbody>
</table>
<p>檢查法：</p>
<ul>
<li>三句內連續三個以上跨領域術語，拆段或補卡。</li>
<li>一句話跨三個以上因果步，拆成多段。</li>
<li>讀者能背結論但說不出推導，補機制、公式、數字或例子。</li>
</ul>
<h3 id="pass-3internal-analysis">Pass 3：Internal Analysis</h3>
<p>Internal analysis 是作者背後的 hypothesis 探索，不等於文章章節。可以用 WRAP 或其他決策框架做內部檢查：</p>
<ul>
<li>要回答什麼問題？</li>
<li>手上 evidence 夠不夠？</li>
<li>有哪些本質不同的合理解釋？</li>
<li>每個解釋的 evidence weight 是什麼？</li>
<li>哪些觀察會推翻本文判讀？</li>
</ul>
<p>這些問題要做完，但不直接當正文標題。正文標題描述讀者會學到什麼，不描述作者正在執行哪個分析步驟。</p>
<h3 id="pass-4title-promise">Pass 4：Title Promise</h3>
<p>Title promise 是標題對讀者做的合約。跑完內部分析後，列出文章每段是否服務標題承諾。</p>
<table>
  <thead>
      <tr>
          <th>檢查項</th>
          <th>行動</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>某段超過 20% 篇幅但不在標題承諾內</td>
          <td>壓縮成背景句、移到別篇、或刪除</td>
      </tr>
      <tr>
          <td>來源 prior 比正文機制更長</td>
          <td>縮減 prior，讓主體回到本文承諾</td>
      </tr>
      <tr>
          <td>標題承諾的是框架、正文卻在解釋作者動機</td>
          <td>改寫主體或重命名標題</td>
      </tr>
  </tbody>
</table>
<p>Title promise pass 保護讀者不被作者的分析過程帶離主題。</p>
<h3 id="pass-5deliverable">Pass 5：Deliverable</h3>
<p>Deliverable pass 確認文章交付的是可遷移框架，而不是原文摘要。結尾至少回答：</p>
<ul>
<li>下次遇到同類事件要看哪些訊號？</li>
<li>哪些機制若成立，本文判讀才成立？</li>
<li>哪些觀察會削弱或推翻本文判讀？</li>
<li>讀者下一步該看哪個概念、卡片或案例？</li>
</ul>
<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></code></pre></div><p>這是責任清單，各文章可以調整順序。不同文章可以調整順序，但不能缺少可遷移框架與失效條件。</p>
<hr>
<h2 id="自檢清單">自檢清單</h2>
<ul>
<li><input disabled="" type="checkbox"> 每個來源句子都能標成事實、原作者判讀、本文推導之一。</li>
<li><input disabled="" type="checkbox"> 原作者 frame 沒有被寫成事件事實。</li>
<li><input disabled="" type="checkbox"> 目標讀者不需要原文 reader contract 也能理解主體推導。</li>
<li><input disabled="" type="checkbox"> 三句內沒有未解釋的連續跨領域術語。</li>
<li><input disabled="" type="checkbox"> 內部分析框架沒有外露成正文標題。</li>
<li><input disabled="" type="checkbox"> 正文段落都服務 title promise。</li>
<li><input disabled="" type="checkbox"> 結尾提供可遷移框架、預警訊號與下一步路由。</li>
</ul>
<hr>
<h2 id="反模式速查">反模式速查</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>修法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>只要求 AI「改成我們的風格」</td>
          <td>改成「抽出可遷移判讀框架」</td>
      </tr>
      <tr>
          <td>把原作者分類直接當本文標題</td>
          <td>標出這是原作者 frame 或本文 frame</td>
      </tr>
      <tr>
          <td>引用機構名但沒有具體來源</td>
          <td>刪具名 attribution 或重新查證</td>
      </tr>
      <tr>
          <td>章節標題是分析 process</td>
          <td>改成教學標題</td>
      </tr>
      <tr>
          <td>文章結尾只有事件結論</td>
          <td>補下次遇到同類事件的判斷問題</td>
      </tr>
      <tr>
          <td>讀者需要懂原領域 shorthand 才懂</td>
          <td>降術語密度，補 bridge sentence</td>
      </tr>
  </tbody>
</table>
<p><strong>核心原則</strong>：外部分析文章改寫的核心是 source transformation。保留事實、分離原作者判讀、重建目標讀者可理解的推導，最後交付可遷移框架。</p>
]]></content:encoded></item><item><title>Personalized Advice Integration</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-personalized-advice/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-personalized-advice/</guid><description>&lt;p>個人化建議整合模式的責任是把 WRAP Step 0 落到具體對話。當建議會因使用者條件而改變時，先確認資料充足度，再進入選項比較。&lt;/p>
&lt;hr>
&lt;h2 id="三層機制">三層機制&lt;/h2>
&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>若答案依使用者而變，進入 Step 0&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>區分通用資訊、假設下建議、個案建議&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="step-0-變數選取">Step 0 變數選取&lt;/h2>
&lt;p>Step 0 變數選取重視決策影響力。好的 Step 0 只問「答案會根本改變建議」的關鍵條件。&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>目標&lt;/td>
 &lt;td>想達成什麼結果&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>限制&lt;/td>
 &lt;td>預算、時間、法規、可用資源&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>環境&lt;/td>
 &lt;td>地區、工具、組織規模、技術棧&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>風險&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;hr>
&lt;h2 id="回答格式">回答格式&lt;/h2>





&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">1. ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">2. ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">3. ...
&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;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="反模式">反模式&lt;/h2>
&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>只問會改變答案的 2-3 個變數&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>分級處理&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;p>&lt;strong>Last Updated&lt;/strong>: 2026-04-30
&lt;strong>Version&lt;/strong>: 2.0.0 — 可攜個人化建議整合模板。&lt;/p></description><content:encoded><![CDATA[<p>個人化建議整合模式的責任是把 WRAP Step 0 落到具體對話。當建議會因使用者條件而改變時，先確認資料充足度，再進入選項比較。</p>
<hr>
<h2 id="三層機制">三層機制</h2>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>問題</th>
          <th>動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>識別</td>
          <td>這是客觀查詢還是個人化建議？</td>
          <td>若答案依使用者而變，進入 Step 0</td>
      </tr>
      <tr>
          <td>分級</td>
          <td>缺資料的後果多嚴重？</td>
          <td>低風險可標假設，中高風險先問關鍵變數</td>
      </tr>
      <tr>
          <td>誠實</td>
          <td>目前能給到什麼程度？</td>
          <td>區分通用資訊、假設下建議、個案建議</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="step-0-變數選取">Step 0 變數選取</h2>
<p>Step 0 變數選取重視決策影響力。好的 Step 0 只問「答案會根本改變建議」的關鍵條件。</p>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>目標</td>
          <td>想達成什麼結果</td>
      </tr>
      <tr>
          <td>限制</td>
          <td>預算、時間、法規、可用資源</td>
      </tr>
      <tr>
          <td>環境</td>
          <td>地區、工具、組織規模、技術棧</td>
      </tr>
      <tr>
          <td>風險</td>
          <td>健康、安全、金錢、法律、資料敏感度</td>
      </tr>
      <tr>
          <td>偏好</td>
          <td>使用者明確重視的取捨</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="回答格式">回答格式</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">目前我可以先給通用判讀；若要給個人化建議，需要先確認：
</span></span><span class="line"><span class="ln">2</span><span class="cl">1. ...
</span></span><span class="line"><span class="ln">3</span><span class="cl">2. ...
</span></span><span class="line"><span class="ln">4</span><span class="cl">3. ...
</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></code></pre></div><hr>
<h2 id="反模式">反模式</h2>
<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>只問會改變答案的 2-3 個變數</td>
      </tr>
      <tr>
          <td>假裝中立</td>
          <td>隱藏建議者偏好</td>
          <td>暴露目前傾向與理由</td>
      </tr>
      <tr>
          <td>把低風險資訊過度升級</td>
          <td>阻礙使用者前進</td>
          <td>分級處理</td>
      </tr>
  </tbody>
</table>
<hr>
<p><strong>Last Updated</strong>: 2026-04-30
<strong>Version</strong>: 2.0.0 — 可攜個人化建議整合模板。</p>
]]></content:encoded></item><item><title>Rules Map Pattern</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-rules-map/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-rules-map/</guid><description>&lt;p>規則庫映射模式的責任是說明 WRAP 與專案規則庫如何分工。WRAP 提供決策品質框架；專案規則庫提供特定工作流的可執行細節。&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>WRAP skill&lt;/td>
 &lt;td>定義何時要擴增選項、驗證假設、拉開距離、準備回退&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Integration pattern&lt;/td>
 &lt;td>提供可攜模板，例如三問、觸發條件、來源核對&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Project rule store&lt;/td>
 &lt;td>定義專案內的實際指令、路徑、owner、狀態欄位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Automation&lt;/td>
 &lt;td>依專案規則執行提醒、檢查或阻擋&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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>Rule name&lt;/td>
 &lt;td>規則名稱&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>WRAP stage&lt;/td>
 &lt;td>對應錨點確認（Anchor）/ Step 0 / W / R / A / P / 絆腳索（Tripwire）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Trigger&lt;/td>
 &lt;td>何時讀這條規則&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Source of truth&lt;/td>
 &lt;td>哪份文件或設定是權威來源&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Automation&lt;/td>
 &lt;td>是否有自動提醒或檢查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Owner&lt;/td>
 &lt;td>誰負責更新&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="維護原則">維護原則&lt;/h2>
&lt;ol>
&lt;li>WRAP skill 不引用專案規則庫。&lt;/li>
&lt;li>專案規則庫可以引用 WRAP skill 與 integration patterns。&lt;/li>
&lt;li>同一條規則只保留一個權威來源。&lt;/li>
&lt;li>自動化工具讀權威來源，不複製清單。&lt;/li>
&lt;li>規則變更時，同步更新範例與反模式。&lt;/li>
&lt;/ol>
&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>核心 skill 寫入專案路徑&lt;/td>
 &lt;td>移到 project rule store&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>多份文件重複觸發條件&lt;/td>
 &lt;td>建立單一真實來源（Source of Truth）與映射表&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>自動化行為與文字規則不同步&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;hr>
&lt;p>&lt;strong>Last Updated&lt;/strong>: 2026-04-30
&lt;strong>Version&lt;/strong>: 2.0.0 — 可攜規則庫映射模板。&lt;/p></description><content:encoded><![CDATA[<p>規則庫映射模式的責任是說明 WRAP 與專案規則庫如何分工。WRAP 提供決策品質框架；專案規則庫提供特定工作流的可執行細節。</p>
<hr>
<h2 id="分工">分工</h2>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>WRAP skill</td>
          <td>定義何時要擴增選項、驗證假設、拉開距離、準備回退</td>
      </tr>
      <tr>
          <td>Integration pattern</td>
          <td>提供可攜模板，例如三問、觸發條件、來源核對</td>
      </tr>
      <tr>
          <td>Project rule store</td>
          <td>定義專案內的實際指令、路徑、owner、狀態欄位</td>
      </tr>
      <tr>
          <td>Automation</td>
          <td>依專案規則執行提醒、檢查或阻擋</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="建議索引欄位">建議索引欄位</h2>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Rule name</td>
          <td>規則名稱</td>
      </tr>
      <tr>
          <td>WRAP stage</td>
          <td>對應錨點確認（Anchor）/ Step 0 / W / R / A / P / 絆腳索（Tripwire）</td>
      </tr>
      <tr>
          <td>Trigger</td>
          <td>何時讀這條規則</td>
      </tr>
      <tr>
          <td>Source of truth</td>
          <td>哪份文件或設定是權威來源</td>
      </tr>
      <tr>
          <td>Automation</td>
          <td>是否有自動提醒或檢查</td>
      </tr>
      <tr>
          <td>Owner</td>
          <td>誰負責更新</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="維護原則">維護原則</h2>
<ol>
<li>WRAP skill 不引用專案規則庫。</li>
<li>專案規則庫可以引用 WRAP skill 與 integration patterns。</li>
<li>同一條規則只保留一個權威來源。</li>
<li>自動化工具讀權威來源，不複製清單。</li>
<li>規則變更時，同步更新範例與反模式。</li>
</ol>
<hr>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>修正</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>核心 skill 寫入專案路徑</td>
          <td>移到 project rule store</td>
      </tr>
      <tr>
          <td>多份文件重複觸發條件</td>
          <td>建立單一真實來源（Source of Truth）與映射表</td>
      </tr>
      <tr>
          <td>自動化行為與文字規則不同步</td>
          <td>讓自動化讀設定來源</td>
      </tr>
      <tr>
          <td>規則只寫禁止，缺替代路徑</td>
          <td>補下一步與升級條件</td>
      </tr>
  </tbody>
</table>
<hr>
<p><strong>Last Updated</strong>: 2026-04-30
<strong>Version</strong>: 2.0.0 — 可攜規則庫映射模板。</p>
]]></content:encoded></item><item><title>Simplified Three Questions</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-simplified-three-questions/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-simplified-three-questions/</guid><description>&lt;p>簡化三問的責任是在任務啟動前保留 WRAP 的最低品質門檻。它取 W/A/P 三個階段，讓小型任務能快速開始，也讓高風險任務及早升級完整 WRAP。&lt;/p>
&lt;hr>
&lt;h2 id="三問">三問&lt;/h2>
&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>W&lt;/td>
 &lt;td>除了第一個方案，還有哪兩條可行路徑？&lt;/td>
 &lt;td>避免預設選項自動勝出&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>A&lt;/td>
 &lt;td>這會擠壓哪個更重要的目標？&lt;/td>
 &lt;td>顯性化機會成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>P&lt;/td>
 &lt;td>如果完成後仍失敗，最可能的三個原因是什麼？&lt;/td>
 &lt;td>提前建立防護與回退&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="填答格式">填答格式&lt;/h2>





&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">W：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">- 首選路徑：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">- 替代路徑 A：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">- 替代路徑 B：
&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：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">- 預估投入：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">- 被擠壓目標：
&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">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">P：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">- 失敗原因 1 / 防護：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">- 失敗原因 2 / 防護：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">- 失敗原因 3 / 防護：&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&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>文件更新&lt;/td>
 &lt;td>三問加既有內容搜尋&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>規則修改&lt;/td>
 &lt;td>三問加悖論檢查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>分析與提案&lt;/td>
 &lt;td>三問只做啟動，接完整 WRAP&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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>W 答不出替代路徑&lt;/td>
 &lt;td>回 W 階段重新擴增&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>A 顯示機會成本偏高&lt;/td>
 &lt;td>暫停並重新排序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>P 任一失敗原因機率偏高&lt;/td>
 &lt;td>補防護或改方案&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>任務涉及重大架構、規則或根因分析&lt;/td>
 &lt;td>跑完整 WRAP&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;p>&lt;strong>Last Updated&lt;/strong>: 2026-04-30
&lt;strong>Version&lt;/strong>: 2.0.0 — 可攜三問模板。&lt;/p></description><content:encoded><![CDATA[<p>簡化三問的責任是在任務啟動前保留 WRAP 的最低品質門檻。它取 W/A/P 三個階段，讓小型任務能快速開始，也讓高風險任務及早升級完整 WRAP。</p>
<hr>
<h2 id="三問">三問</h2>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>問題</th>
          <th>目的</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>W</td>
          <td>除了第一個方案，還有哪兩條可行路徑？</td>
          <td>避免預設選項自動勝出</td>
      </tr>
      <tr>
          <td>A</td>
          <td>這會擠壓哪個更重要的目標？</td>
          <td>顯性化機會成本</td>
      </tr>
      <tr>
          <td>P</td>
          <td>如果完成後仍失敗，最可能的三個原因是什麼？</td>
          <td>提前建立防護與回退</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="填答格式">填答格式</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">W：
</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：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 替代路徑 B：
</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：
</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">
</span></span><span class="line"><span class="ln">12</span><span class="cl">P：
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 失敗原因 1 / 防護：
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 失敗原因 2 / 防護：
</span></span><span class="line"><span class="ln">15</span><span class="cl">- 失敗原因 3 / 防護：</span></span></code></pre></div><hr>
<h2 id="適用範圍">適用範圍</h2>
<table>
  <thead>
      <tr>
          <th>任務</th>
          <th>適用方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>小型實作</td>
          <td>三問通過即可開始</td>
      </tr>
      <tr>
          <td>文件更新</td>
          <td>三問加既有內容搜尋</td>
      </tr>
      <tr>
          <td>規則修改</td>
          <td>三問加悖論檢查</td>
      </tr>
      <tr>
          <td>分析與提案</td>
          <td>三問只做啟動，接完整 WRAP</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="升級條件">升級條件</h2>
<table>
  <thead>
      <tr>
          <th>條件</th>
          <th>動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>W 答不出替代路徑</td>
          <td>回 W 階段重新擴增</td>
      </tr>
      <tr>
          <td>A 顯示機會成本偏高</td>
          <td>暫停並重新排序</td>
      </tr>
      <tr>
          <td>P 任一失敗原因機率偏高</td>
          <td>補防護或改方案</td>
      </tr>
      <tr>
          <td>任務涉及重大架構、規則或根因分析</td>
          <td>跑完整 WRAP</td>
      </tr>
  </tbody>
</table>
<hr>
<p><strong>Last Updated</strong>: 2026-04-30
<strong>Version</strong>: 2.0.0 — 可攜三問模板。</p>
]]></content:encoded></item><item><title>Source Verification Pattern</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-source-verification/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-source-verification/</guid><description>&lt;p>來源核對模式的責任是防止清單類答案被整批信任。它要求逐項對照來源，並把找不到來源的項目標成候選幻覺。&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>LLM 列出 API、欄位、規則或功能清單&lt;/td>
 &lt;td>可能補齊不存在的項目&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Agent 回報某套工具的能力&lt;/td>
 &lt;td>可能混入相似工具的功能&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>文章引用外部標準或框架&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;hr>
&lt;h2 id="核對流程">核對流程&lt;/h2>
&lt;ol>
&lt;li>找到權威來源：官方文件、本機 schema、原始設定、標準文件或實際程式碼。&lt;/li>
&lt;li>拆成逐項清單：每個欄位、能力、規則或聲明各自核對。&lt;/li>
&lt;li>標記來源狀態：confirmed、inferred、missing、conflict。&lt;/li>
&lt;li>對 missing 與 conflict 項目重新查證或移除。&lt;/li>
&lt;li>在輸出中標示哪些是來源事實，哪些是分析推論。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="來源狀態表">來源狀態表&lt;/h2>
&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>confirmed&lt;/td>
 &lt;td>來源明確支持&lt;/td>
 &lt;td>可以&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>inferred&lt;/td>
 &lt;td>來源間接支持，需要推論&lt;/td>
 &lt;td>可以，但需標示推論&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>missing&lt;/td>
 &lt;td>找不到來源&lt;/td>
 &lt;td>不作為結論&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>conflict&lt;/td>
 &lt;td>來源互相矛盾&lt;/td>
 &lt;td>先釐清版本或範圍&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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>把相似工具功能混用&lt;/td>
 &lt;td>只引用當前工具來源&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>把最佳實務寫成標準要求&lt;/td>
 &lt;td>區分 recommendation 與 requirement&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>把推論包裝成引用&lt;/td>
 &lt;td>明確標示「由來源推論」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;p>&lt;strong>Last Updated&lt;/strong>: 2026-04-30
&lt;strong>Version&lt;/strong>: 2.0.0 — 可攜來源核對模板。&lt;/p></description><content:encoded><![CDATA[<p>來源核對模式的責任是防止清單類答案被整批信任。它要求逐項對照來源，並把找不到來源的項目標成候選幻覺。</p>
<hr>
<h2 id="適用情境">適用情境</h2>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LLM 列出 API、欄位、規則或功能清單</td>
          <td>可能補齊不存在的項目</td>
      </tr>
      <tr>
          <td>Agent 回報某套工具的能力</td>
          <td>可能混入相似工具的功能</td>
      </tr>
      <tr>
          <td>文章引用外部標準或框架</td>
          <td>可能把推論寫成來源事實</td>
      </tr>
      <tr>
          <td>規則設計依賴既有設定</td>
          <td>可能讀到過期或錯誤記憶</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="核對流程">核對流程</h2>
<ol>
<li>找到權威來源：官方文件、本機 schema、原始設定、標準文件或實際程式碼。</li>
<li>拆成逐項清單：每個欄位、能力、規則或聲明各自核對。</li>
<li>標記來源狀態：confirmed、inferred、missing、conflict。</li>
<li>對 missing 與 conflict 項目重新查證或移除。</li>
<li>在輸出中標示哪些是來源事實，哪些是分析推論。</li>
</ol>
<hr>
<h2 id="來源狀態表">來源狀態表</h2>
<table>
  <thead>
      <tr>
          <th>狀態</th>
          <th>意義</th>
          <th>可否作為結論</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>confirmed</td>
          <td>來源明確支持</td>
          <td>可以</td>
      </tr>
      <tr>
          <td>inferred</td>
          <td>來源間接支持，需要推論</td>
          <td>可以，但需標示推論</td>
      </tr>
      <tr>
          <td>missing</td>
          <td>找不到來源</td>
          <td>不作為結論</td>
      </tr>
      <tr>
          <td>conflict</td>
          <td>來源互相矛盾</td>
          <td>先釐清版本或範圍</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>修正</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>找到一個來源就整批信任</td>
          <td>每一項逐項核對</td>
      </tr>
      <tr>
          <td>把相似工具功能混用</td>
          <td>只引用當前工具來源</td>
      </tr>
      <tr>
          <td>把最佳實務寫成標準要求</td>
          <td>區分 recommendation 與 requirement</td>
      </tr>
      <tr>
          <td>把推論包裝成引用</td>
          <td>明確標示「由來源推論」</td>
      </tr>
  </tbody>
</table>
<hr>
<p><strong>Last Updated</strong>: 2026-04-30
<strong>Version</strong>: 2.0.0 — 可攜來源核對模板。</p>
]]></content:encoded></item><item><title>Translation Review — 文章翻譯與轉譯檢查</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/translation-review/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/translation-review/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>本檔位置&lt;/strong>：&lt;code>compositional-writing&lt;/code> 的文章轉譯 / 翻譯 / 術語改寫 reference。&lt;/p>
&lt;p>&lt;strong>何時讀&lt;/strong>：把英文材料轉成中文、把既有文章改寫成另一種語言、整理 AI / 工程 / 方法論術語，或 reviewer 指出「這個翻譯放在句子裡怪怪的」時。&lt;/p>&lt;/blockquote>
&lt;h2 id="核心命題">核心命題&lt;/h2>
&lt;p>翻譯 review 的核心是句內邏輯對位，不是詞典對位。翻譯完成後要把譯名放回原句，檢查它跟主詞、動詞、修飾語、因果關係是否仍然成立；如果譯名讓句子多出原文沒有的前提，或讓讀者追問方向改變，這個翻譯就有問題。&lt;/p>
&lt;p>翻譯錯誤常在中文很順時被放過。順口只代表中文語感成立，不代表原文概念、句內角色、後續推論成立。多輪檢查時要把「順不順」跟「邏輯是否對位」分開看。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼文章轉譯容易誤譯">為什麼文章轉譯容易誤譯&lt;/h2>
&lt;p>文章轉譯容易誤譯，是因為譯者會同時壓縮三件事：讀懂原文、找中文詞、讓句子順。這三件事會互相干擾，尤其在 AI / 工程 / 方法論文章中，原文詞常同時有字根、日常語意、術語語意。&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>順口可能只是中文搭配成立，不代表邏輯成立&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>常見譯名記憶&lt;/td>
 &lt;td>以前看過類似翻法&lt;/td>
 &lt;td>不同領域同詞不同義，舊翻法可能換場景失效&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AI 自動補詞&lt;/td>
 &lt;td>模型會補出完整片語&lt;/td>
 &lt;td>補出的中文可能比原文多一層未明說的前提&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>翻譯 review 的任務是把這些便利訊號重新拆開。先問原文概念在句子中承擔什麼責任，再決定中文詞是否能承擔同一個責任。&lt;/p>
&lt;hr>
&lt;h2 id="句內邏輯檢查">句內邏輯檢查&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>主詞角色&lt;/td>
 &lt;td>這個詞描述的是誰的行為或關係？&lt;/td>
 &lt;td>句子在談 A，譯名把它帶成 B&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>修飾語找不到原文來源&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>受詞邊界&lt;/td>
 &lt;td>這個詞作用到的對象是否一樣？&lt;/td>
 &lt;td>原文作用在 decision，中文變成 person&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>因果方向&lt;/td>
 &lt;td>這個詞是否支撐段落原本的因果？&lt;/td>
 &lt;td>原本「因為 X」被譯名暗示成「因為 Y」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>讀者追問方向&lt;/td>
 &lt;td>reader 看到這個詞會問哪個問題？&lt;/td>
 &lt;td>追問方向偏離原文核心&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>檢查時可以把原句拆成一句話：「因為 X，所以 Y」。若中文譯名把 X 換成 X&amp;rsquo;，後面 Y 還能成立嗎？不能成立就要重譯。&lt;/p>
&lt;hr>
&lt;h2 id="casepaternalism-不是父權式保護">Case：paternalism 不是父權式保護&lt;/h2>
&lt;p>&lt;code>paternalism&lt;/code> 被翻成「父權式保護」的問題是句內邏輯錯位。這個譯名同時抓到 &lt;code>paternal&lt;/code> 的父系字根與「保護他人」的語境，所以中文看似能讀；但放回句子後，它讓句子多出原文沒有的前提：這裡似乎涉及父權或性別權力結構。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>檢查層&lt;/th>
 &lt;th>&lt;code>父權式保護&lt;/code> 的問題&lt;/th>
 &lt;th>較合理的對位&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>主詞角色&lt;/td>
 &lt;td>段落在談規則對自主性的介入，不是在談父權&lt;/td>
 &lt;td>家長主義（paternalism）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>修飾語關係&lt;/td>
 &lt;td>「父權式」引入 gender / patriarchy 前提&lt;/td>
 &lt;td>「家長」對位替對方決定何者對他好&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>動詞搭配&lt;/td>
 &lt;td>「通過父權式保護 4 條件測試」語意卡住&lt;/td>
 &lt;td>「通過家長主義 4 條件測試」可承接&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>reader 會問「父權在哪？」&lt;/td>
 &lt;td>reader 會問「自主性與介入邊界在哪？」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>修法的重點是補定義句讓邏輯回到原文責任：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">家長主義（paternalism）在本文指「以對方利益之名限制對方自主」。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">常見譯名也包含「家長作風」；本文統一用「家長主義」。&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="casesteelman-不是最強版本測試">Case：Steelman 不是最強版本測試&lt;/h2>
&lt;p>&lt;code>Steelman&lt;/code> 被翻成「最強版本測試」的問題是概念角色被換掉。這個譯名在 checklist 語境裡看似自然，因為 reviewer 確實會問「有沒有通過」；但 &lt;code>Steelman&lt;/code> 的核心責任是把被放棄選項或反方立場重建成最有力版本。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>檢查層&lt;/th>
 &lt;th>&lt;code>最強版本測試&lt;/code> 的問題&lt;/th>
 &lt;th>較合理的對位&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>來源角色&lt;/td>
 &lt;td>把論證方法壓成檢查動作&lt;/td>
 &lt;td>最強版本論證（Steelman）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>本文角色&lt;/td>
 &lt;td>只保留 checklist 用途&lt;/td>
 &lt;td>保留降低確認偏誤的推理方法&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>動詞搭配&lt;/td>
 &lt;td>reader 追問「怎樣算測試通過」&lt;/td>
 &lt;td>reader 追問「反方是否重建」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Surface 面&lt;/td>
 &lt;td>heading / checklist 會傳播錯誤角色&lt;/td>
 &lt;td>全文統一同一概念角色&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>修法的重點是保留概念角色：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">最強版本論證（Steelman）在本文指：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">先把被放棄選項或反方立場重建成最有力版本，再檢查自己的選擇是否仍站得住腳。&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="多輪檢查流程">多輪檢查流程&lt;/h2>
&lt;p>翻譯 review 要獨立成一輪，放在一般命名 / grep-ability 檢查附近。它不取代文章的意圖檢查、語氣檢查或邊界檢查，而是補上「語言轉換後邏輯是否仍成立」這個角度。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Pass&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>標記英文材料、AI 產出、改寫段落&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2. 術語枚舉&lt;/td>
 &lt;td>哪些中文詞由英文概念翻來？&lt;/td>
 &lt;td>grep 英文括號、標題、表格第一欄、index entry&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3. 原文錨點&lt;/td>
 &lt;td>第一次出現是否保留 original term？&lt;/td>
 &lt;td>補「中文術語（original term）」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4. 句內角色&lt;/td>
 &lt;td>中文詞在句中扮演的角色是否對？&lt;/td>
 &lt;td>標出主詞、動詞、受詞、修飾語&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>5. 前提檢查&lt;/td>
 &lt;td>中文是否多出原文沒有的前提？&lt;/td>
 &lt;td>問「這個修飾語 / 判斷從原文哪裡來？」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>6. 因果檢查&lt;/td>
 &lt;td>譯名是否改變段落推論方向？&lt;/td>
 &lt;td>重寫「因為 X，所以 Y」，確認 X 沒被換掉&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>7. 追問檢查&lt;/td>
 &lt;td>reader 看到中文會追問正確問題嗎？&lt;/td>
 &lt;td>問「他會問 A 還是 B？」；問錯方向就重譯&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>8. Surface 同步&lt;/td>
 &lt;td>metadata / navigation 是否也對位？&lt;/td>
 &lt;td>掃 title、description、heading、link label&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這輪可以很短，但不能省。文章轉譯時，最容易被放過的錯誤就是「中文通順、邏輯錯位」。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p><strong>本檔位置</strong>：<code>compositional-writing</code> 的文章轉譯 / 翻譯 / 術語改寫 reference。</p>
<p><strong>何時讀</strong>：把英文材料轉成中文、把既有文章改寫成另一種語言、整理 AI / 工程 / 方法論術語，或 reviewer 指出「這個翻譯放在句子裡怪怪的」時。</p></blockquote>
<h2 id="核心命題">核心命題</h2>
<p>翻譯 review 的核心是句內邏輯對位，不是詞典對位。翻譯完成後要把譯名放回原句，檢查它跟主詞、動詞、修飾語、因果關係是否仍然成立；如果譯名讓句子多出原文沒有的前提，或讓讀者追問方向改變，這個翻譯就有問題。</p>
<p>翻譯錯誤常在中文很順時被放過。順口只代表中文語感成立，不代表原文概念、句內角色、後續推論成立。多輪檢查時要把「順不順」跟「邏輯是否對位」分開看。</p>
<hr>
<h2 id="為什麼文章轉譯容易誤譯">為什麼文章轉譯容易誤譯</h2>
<p>文章轉譯容易誤譯，是因為譯者會同時壓縮三件事：讀懂原文、找中文詞、讓句子順。這三件事會互相干擾，尤其在 AI / 工程 / 方法論文章中，原文詞常同時有字根、日常語意、術語語意。</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>順口可能只是中文搭配成立，不代表邏輯成立</td>
      </tr>
      <tr>
          <td>常見譯名記憶</td>
          <td>以前看過類似翻法</td>
          <td>不同領域同詞不同義，舊翻法可能換場景失效</td>
      </tr>
      <tr>
          <td>AI 自動補詞</td>
          <td>模型會補出完整片語</td>
          <td>補出的中文可能比原文多一層未明說的前提</td>
      </tr>
  </tbody>
</table>
<p>翻譯 review 的任務是把這些便利訊號重新拆開。先問原文概念在句子中承擔什麼責任，再決定中文詞是否能承擔同一個責任。</p>
<hr>
<h2 id="句內邏輯檢查">句內邏輯檢查</h2>
<p>句內邏輯檢查要求把翻譯後的中文詞放回原句，逐項檢查語法角色與論證角色。這一步比查字典更早，因為很多錯譯不是「查不到正確譯名」，而是譯名放進句子後改變了原句的邏輯。</p>
<table>
  <thead>
      <tr>
          <th>檢查層</th>
          <th>問題</th>
          <th>失效訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>主詞角色</td>
          <td>這個詞描述的是誰的行為或關係？</td>
          <td>句子在談 A，譯名把它帶成 B</td>
      </tr>
      <tr>
          <td>動詞搭配</td>
          <td>這個詞能自然承接後面的動作嗎？</td>
          <td>放回原句後語意卡住</td>
      </tr>
      <tr>
          <td>修飾語關係</td>
          <td>中文修飾語是否引入額外前提？</td>
          <td>修飾語找不到原文來源</td>
      </tr>
      <tr>
          <td>受詞邊界</td>
          <td>這個詞作用到的對象是否一樣？</td>
          <td>原文作用在 decision，中文變成 person</td>
      </tr>
      <tr>
          <td>因果方向</td>
          <td>這個詞是否支撐段落原本的因果？</td>
          <td>原本「因為 X」被譯名暗示成「因為 Y」</td>
      </tr>
      <tr>
          <td>讀者追問方向</td>
          <td>reader 看到這個詞會問哪個問題？</td>
          <td>追問方向偏離原文核心</td>
      </tr>
  </tbody>
</table>
<p>檢查時可以把原句拆成一句話：「因為 X，所以 Y」。若中文譯名把 X 換成 X&rsquo;，後面 Y 還能成立嗎？不能成立就要重譯。</p>
<hr>
<h2 id="casepaternalism-不是父權式保護">Case：paternalism 不是父權式保護</h2>
<p><code>paternalism</code> 被翻成「父權式保護」的問題是句內邏輯錯位。這個譯名同時抓到 <code>paternal</code> 的父系字根與「保護他人」的語境，所以中文看似能讀；但放回句子後，它讓句子多出原文沒有的前提：這裡似乎涉及父權或性別權力結構。</p>
<table>
  <thead>
      <tr>
          <th>檢查層</th>
          <th><code>父權式保護</code> 的問題</th>
          <th>較合理的對位</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>主詞角色</td>
          <td>段落在談規則對自主性的介入，不是在談父權</td>
          <td>家長主義（paternalism）</td>
      </tr>
      <tr>
          <td>修飾語關係</td>
          <td>「父權式」引入 gender / patriarchy 前提</td>
          <td>「家長」對位替對方決定何者對他好</td>
      </tr>
      <tr>
          <td>動詞搭配</td>
          <td>「通過父權式保護 4 條件測試」語意卡住</td>
          <td>「通過家長主義 4 條件測試」可承接</td>
      </tr>
      <tr>
          <td>因果方向</td>
          <td>原因是替對方決定，不是父權結構</td>
          <td>因為限制自主，所以要檢查介入正當性</td>
      </tr>
      <tr>
          <td>讀者追問方向</td>
          <td>reader 會問「父權在哪？」</td>
          <td>reader 會問「自主性與介入邊界在哪？」</td>
      </tr>
  </tbody>
</table>
<p>修法的重點是補定義句讓邏輯回到原文責任：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">家長主義（paternalism）在本文指「以對方利益之名限制對方自主」。
</span></span><span class="line"><span class="ln">2</span><span class="cl">常見譯名也包含「家長作風」；本文統一用「家長主義」。</span></span></code></pre></div><h2 id="casesteelman-不是最強版本測試">Case：Steelman 不是最強版本測試</h2>
<p><code>Steelman</code> 被翻成「最強版本測試」的問題是概念角色被換掉。這個譯名在 checklist 語境裡看似自然，因為 reviewer 確實會問「有沒有通過」；但 <code>Steelman</code> 的核心責任是把被放棄選項或反方立場重建成最有力版本。</p>
<table>
  <thead>
      <tr>
          <th>檢查層</th>
          <th><code>最強版本測試</code> 的問題</th>
          <th>較合理的對位</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>來源角色</td>
          <td>把論證方法壓成檢查動作</td>
          <td>最強版本論證（Steelman）</td>
      </tr>
      <tr>
          <td>本文角色</td>
          <td>只保留 checklist 用途</td>
          <td>保留降低確認偏誤的推理方法</td>
      </tr>
      <tr>
          <td>動詞搭配</td>
          <td>reader 追問「怎樣算測試通過」</td>
          <td>reader 追問「反方是否重建」</td>
      </tr>
      <tr>
          <td>Surface 面</td>
          <td>heading / checklist 會傳播錯誤角色</td>
          <td>全文統一同一概念角色</td>
      </tr>
  </tbody>
</table>
<p>修法的重點是保留概念角色：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">最強版本論證（Steelman）在本文指：
</span></span><span class="line"><span class="ln">2</span><span class="cl">先把被放棄選項或反方立場重建成最有力版本，再檢查自己的選擇是否仍站得住腳。</span></span></code></pre></div><hr>
<h2 id="多輪檢查流程">多輪檢查流程</h2>
<p>翻譯 review 要獨立成一輪，放在一般命名 / grep-ability 檢查附近。它不取代文章的意圖檢查、語氣檢查或邊界檢查，而是補上「語言轉換後邏輯是否仍成立」這個角度。</p>
<table>
  <thead>
      <tr>
          <th>Pass</th>
          <th>問題</th>
          <th>操作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. 來源定位</td>
          <td>哪些句子來自翻譯 / 轉譯？</td>
          <td>標記英文材料、AI 產出、改寫段落</td>
      </tr>
      <tr>
          <td>2. 術語枚舉</td>
          <td>哪些中文詞由英文概念翻來？</td>
          <td>grep 英文括號、標題、表格第一欄、index entry</td>
      </tr>
      <tr>
          <td>3. 原文錨點</td>
          <td>第一次出現是否保留 original term？</td>
          <td>補「中文術語（original term）」</td>
      </tr>
      <tr>
          <td>4. 句內角色</td>
          <td>中文詞在句中扮演的角色是否對？</td>
          <td>標出主詞、動詞、受詞、修飾語</td>
      </tr>
      <tr>
          <td>5. 前提檢查</td>
          <td>中文是否多出原文沒有的前提？</td>
          <td>問「這個修飾語 / 判斷從原文哪裡來？」</td>
      </tr>
      <tr>
          <td>6. 因果檢查</td>
          <td>譯名是否改變段落推論方向？</td>
          <td>重寫「因為 X，所以 Y」，確認 X 沒被換掉</td>
      </tr>
      <tr>
          <td>7. 追問檢查</td>
          <td>reader 看到中文會追問正確問題嗎？</td>
          <td>問「他會問 A 還是 B？」；問錯方向就重譯</td>
      </tr>
      <tr>
          <td>8. Surface 同步</td>
          <td>metadata / navigation 是否也對位？</td>
          <td>掃 title、description、heading、link label</td>
      </tr>
  </tbody>
</table>
<p>這輪可以很短，但不能省。文章轉譯時，最容易被放過的錯誤就是「中文通順、邏輯錯位」。</p>
<hr>
<h2 id="原文錨點與完整名詞頭">原文錨點與完整名詞頭</h2>
<p>原文錨點負責保留概念邊界，完整名詞頭負責讓中文術語離開原句仍能獨立成立。兩者是翻譯 review 的輔助工具，不是最終目的；最終目的仍是句內邏輯對位。</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>解決什麼問題</th>
          <th>檢查問題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>原文錨點</td>
          <td>中文譯名可能偏離原概念</td>
          <td>第一次出現是否保留 original term</td>
      </tr>
      <tr>
          <td>完整名詞頭</td>
          <td>中文壓縮後像片語殘片</td>
          <td>這個詞是否能回答「這是什麼」</td>
      </tr>
      <tr>
          <td>定義句</td>
          <td>reader 不知道本文採用哪個概念邊界</td>
          <td>是否用一句話說明本文用法</td>
      </tr>
      <tr>
          <td>常見譯名段</td>
          <td>同一英文有多個中文譯名</td>
          <td>是否列出但選一個 canonical</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>症狀</th>
          <th>修法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>字根直譯</td>
          <td>中文每個字都像對，但整句邏輯多出前提</td>
          <td>回到句內角色與概念責任</td>
      </tr>
      <tr>
          <td>中文順口即放行</td>
          <td>reviewer 覺得好讀，後面論證卻換了問題</td>
          <td>加句內邏輯 pass</td>
      </tr>
      <tr>
          <td>只查字典不放回原句</td>
          <td>譯名本身可用，放進本文語境不成立</td>
          <td>用本文句子重新測試</td>
      </tr>
      <tr>
          <td>只保留原文但不定義</td>
          <td>reader 知道英文，仍不知道本文採用哪個邊界</td>
          <td>補本文定義句</td>
      </tr>
      <tr>
          <td>只改正文、不改 navigation</td>
          <td>title / heading / link label 仍傳播舊譯名</td>
          <td>跑 surface 同步</td>
      </tr>
      <tr>
          <td>AI 翻譯後只做潤稿</td>
          <td>潤稿讓錯譯更順，反而更難發現</td>
          <td>潤稿前先做翻譯邏輯 review</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="最小檢查表">最小檢查表</h2>
<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，所以 Y」，且 X 沒被譯名換掉</li>
<li><input disabled="" type="checkbox"> reader 看到中文詞會追問原文核心問題</li>
<li><input disabled="" type="checkbox"> title、heading、description、link label 沒有殘留舊譯名</li>
</ul>
<p><strong>核心</strong>：翻譯 review 的核心是確認語言轉換後，句子仍在說同一件事。</p>
]]></content:encoded></item><item><title>Triggers Alignment Pattern</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-triggers-alignment/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-triggers-alignment/</guid><description>&lt;p>觸發條件對齊模式的責任是讓文字規則、機器設定與自動提醒共享同一組語意。它避免多處複述造成清單漂移，也讓每個專案能用自己的工具實作 WRAP reminder。&lt;/p>
&lt;hr>
&lt;h2 id="三層分工">三層分工&lt;/h2>
&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>人類規則（Human rule）&lt;/td>
 &lt;td>說明何時需要 WRAP&lt;/td>
 &lt;td>SKILL.md 觸發條件表&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>機器設定（Machine config）&lt;/td>
 &lt;td>儲存可機器讀取的關鍵字、閾值、事件類型&lt;/td>
 &lt;td>JSON/YAML/TOML 設定檔&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>自動化掛鉤（Automation hook）&lt;/td>
 &lt;td>讀取設定並在適當時機提醒&lt;/td>
 &lt;td>pre-task hook、CLI middleware、chat assistant guard&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="對齊規則">對齊規則&lt;/h2>
&lt;ol>
&lt;li>文字規則定義語意，不重複機器清單。&lt;/li>
&lt;li>機器設定保存關鍵字與閾值，作為自動化來源。&lt;/li>
&lt;li>自動提醒只讀設定，不硬編碼第二份清單。&lt;/li>
&lt;li>每次新增觸發類型，都同步更新 human rule 與 machine config 的映射說明。&lt;/li>
&lt;/ol>
&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;code>trigger_id&lt;/code>&lt;/td>
 &lt;td>穩定識別碼&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>human_description&lt;/code>&lt;/td>
 &lt;td>人類可讀語意&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>signals&lt;/code>&lt;/td>
 &lt;td>關鍵字、事件或狀態&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>threshold&lt;/code>&lt;/td>
 &lt;td>次數、時間或嚴重度門檻&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>recommended_action&lt;/code>&lt;/td>
 &lt;td>提醒後要做的下一步&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>escalation&lt;/code>&lt;/td>
 &lt;td>何時從快速模式（quick mode）升級完整 WRAP&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="反模式">反模式&lt;/h2>
&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>掛鉤（Hook）內硬編碼關鍵字&lt;/td>
 &lt;td>更新漏改&lt;/td>
 &lt;td>掛鉤（Hook）讀設定檔&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>只提醒不給下一步&lt;/td>
 &lt;td>使用者忽略提醒&lt;/td>
 &lt;td>附上快速 WRAP（Quick WRAP）或完整 WRAP 路由&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>提醒過於頻繁&lt;/td>
 &lt;td>形成噪音&lt;/td>
 &lt;td>加閾值、冷卻時間與升級條件&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;p>&lt;strong>Last Updated&lt;/strong>: 2026-04-30
&lt;strong>Version&lt;/strong>: 2.0.0 — 可攜觸發條件對齊模板。&lt;/p></description><content:encoded><![CDATA[<p>觸發條件對齊模式的責任是讓文字規則、機器設定與自動提醒共享同一組語意。它避免多處複述造成清單漂移，也讓每個專案能用自己的工具實作 WRAP reminder。</p>
<hr>
<h2 id="三層分工">三層分工</h2>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>責任</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>人類規則（Human rule）</td>
          <td>說明何時需要 WRAP</td>
          <td>SKILL.md 觸發條件表</td>
      </tr>
      <tr>
          <td>機器設定（Machine config）</td>
          <td>儲存可機器讀取的關鍵字、閾值、事件類型</td>
          <td>JSON/YAML/TOML 設定檔</td>
      </tr>
      <tr>
          <td>自動化掛鉤（Automation hook）</td>
          <td>讀取設定並在適當時機提醒</td>
          <td>pre-task hook、CLI middleware、chat assistant guard</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="對齊規則">對齊規則</h2>
<ol>
<li>文字規則定義語意，不重複機器清單。</li>
<li>機器設定保存關鍵字與閾值，作為自動化來源。</li>
<li>自動提醒只讀設定，不硬編碼第二份清單。</li>
<li>每次新增觸發類型，都同步更新 human rule 與 machine config 的映射說明。</li>
</ol>
<hr>
<h2 id="建議欄位">建議欄位</h2>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>trigger_id</code></td>
          <td>穩定識別碼</td>
      </tr>
      <tr>
          <td><code>human_description</code></td>
          <td>人類可讀語意</td>
      </tr>
      <tr>
          <td><code>signals</code></td>
          <td>關鍵字、事件或狀態</td>
      </tr>
      <tr>
          <td><code>threshold</code></td>
          <td>次數、時間或嚴重度門檻</td>
      </tr>
      <tr>
          <td><code>recommended_action</code></td>
          <td>提醒後要做的下一步</td>
      </tr>
      <tr>
          <td><code>escalation</code></td>
          <td>何時從快速模式（quick mode）升級完整 WRAP</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>風險</th>
          <th>修正</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>規則與設定各自列清單</td>
          <td>版本漂移</td>
          <td>建立映射表與單一機器來源</td>
      </tr>
      <tr>
          <td>掛鉤（Hook）內硬編碼關鍵字</td>
          <td>更新漏改</td>
          <td>掛鉤（Hook）讀設定檔</td>
      </tr>
      <tr>
          <td>只提醒不給下一步</td>
          <td>使用者忽略提醒</td>
          <td>附上快速 WRAP（Quick WRAP）或完整 WRAP 路由</td>
      </tr>
      <tr>
          <td>提醒過於頻繁</td>
          <td>形成噪音</td>
          <td>加閾值、冷卻時間與升級條件</td>
      </tr>
  </tbody>
</table>
<hr>
<p><strong>Last Updated</strong>: 2026-04-30
<strong>Version</strong>: 2.0.0 — 可攜觸發條件對齊模板。</p>
]]></content:encoded></item><item><title>WRAP Case Study Pattern</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-case-studies/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-case-studies/</guid><description>&lt;p>案例庫模式的責任是把實戰事件轉成可重用的決策教訓。案例不依賴原專案背景也要能被理解，重點是抽出觸發訊號、錯誤判斷、WRAP 介入點與後續防護。&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>案例標題（Case title）&lt;/td>
 &lt;td>一句話說明事件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Situation&lt;/td>
 &lt;td>當時要完成的目標&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Trigger&lt;/td>
 &lt;td>為什麼需要 WRAP&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>錯誤路徑（Faulty path）&lt;/td>
 &lt;td>原本自動駕駛的路徑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>WRAP observation&lt;/td>
 &lt;td>WRAP 揭露了什麼選項、證據或成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>改善路徑（Better path）&lt;/td>
 &lt;td>調整後的做法&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>防護欄（Guardrail）&lt;/td>
 &lt;td>後續新增的檢查、規則或提醒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>重用訊號（Reuse signal）&lt;/td>
 &lt;td>未來遇到什麼情境要重讀本案例&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="抽象化規則">抽象化規則&lt;/h2>
&lt;ol>
&lt;li>移除專案私有路徑、內部編號與人名。&lt;/li>
&lt;li>保留決策結構：目標、選項、證據、成本、回退。&lt;/li>
&lt;li>把工具名稱改成中性角色，例如 task runner、rule store、automation hook。&lt;/li>
&lt;li>若案例依賴特定工具，改寫成「某 CLI」「某任務系統」「某文件產生流程」。&lt;/li>
&lt;li>每個案例最後要有可 grep 的 reuse signal。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="案例模板">案例模板&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="gu">## {case-title}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="gs">**Situation**&lt;/span>：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="gs">**Trigger**&lt;/span>：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="gs">**錯誤路徑（Faulty path）**&lt;/span>：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="gs">**WRAP observation**&lt;/span>：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="gs">**改善路徑（Better path）**&lt;/span>：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="gs">**防護欄（Guardrail）**&lt;/span>：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="gs">**重用訊號（Reuse signal）**&lt;/span>：...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&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>過早收斂（Premature convergence）&lt;/td>
 &lt;td>過早相信第一個根因&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>偽擴增選項（Pseudo widen）&lt;/td>
 &lt;td>多個選項仍指向同一假設&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>來源幻覺（Source hallucination）&lt;/td>
 &lt;td>清單項目未逐項核對來源&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>工具化偏誤（Toolification）&lt;/td>
 &lt;td>把新增工具當成預設答案&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>家長主義悖論（Paternalism paradox）&lt;/td>
 &lt;td>為保護使用者而過度限制使用者&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>絆腳索疲乏（Tripwire fatigue）&lt;/td>
 &lt;td>提醒太頻繁導致被忽略&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;p>&lt;strong>Last Updated&lt;/strong>: 2026-04-30
&lt;strong>Version&lt;/strong>: 2.0.0 — 可攜案例庫模板。&lt;/p></description><content:encoded><![CDATA[<p>案例庫模式的責任是把實戰事件轉成可重用的決策教訓。案例不依賴原專案背景也要能被理解，重點是抽出觸發訊號、錯誤判斷、WRAP 介入點與後續防護。</p>
<hr>
<h2 id="收錄欄位">收錄欄位</h2>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>案例標題（Case title）</td>
          <td>一句話說明事件</td>
      </tr>
      <tr>
          <td>Situation</td>
          <td>當時要完成的目標</td>
      </tr>
      <tr>
          <td>Trigger</td>
          <td>為什麼需要 WRAP</td>
      </tr>
      <tr>
          <td>錯誤路徑（Faulty path）</td>
          <td>原本自動駕駛的路徑</td>
      </tr>
      <tr>
          <td>WRAP observation</td>
          <td>WRAP 揭露了什麼選項、證據或成本</td>
      </tr>
      <tr>
          <td>改善路徑（Better path）</td>
          <td>調整後的做法</td>
      </tr>
      <tr>
          <td>防護欄（Guardrail）</td>
          <td>後續新增的檢查、規則或提醒</td>
      </tr>
      <tr>
          <td>重用訊號（Reuse signal）</td>
          <td>未來遇到什麼情境要重讀本案例</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="抽象化規則">抽象化規則</h2>
<ol>
<li>移除專案私有路徑、內部編號與人名。</li>
<li>保留決策結構：目標、選項、證據、成本、回退。</li>
<li>把工具名稱改成中性角色，例如 task runner、rule store、automation hook。</li>
<li>若案例依賴特定工具，改寫成「某 CLI」「某任務系統」「某文件產生流程」。</li>
<li>每個案例最後要有可 grep 的 reuse signal。</li>
</ol>
<hr>
<h2 id="案例模板">案例模板</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">## {case-title}
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="gs">**Situation**</span>：...
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="gs">**Trigger**</span>：...
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="gs">**錯誤路徑（Faulty path）**</span>：...
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="gs">**WRAP observation**</span>：...
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="gs">**改善路徑（Better path）**</span>：...
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="gs">**防護欄（Guardrail）**</span>：...
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="gs">**重用訊號（Reuse signal）**</span>：...</span></span></code></pre></div><hr>
<h2 id="常見案例類型">常見案例類型</h2>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>典型教訓</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>過早收斂（Premature convergence）</td>
          <td>過早相信第一個根因</td>
      </tr>
      <tr>
          <td>偽擴增選項（Pseudo widen）</td>
          <td>多個選項仍指向同一假設</td>
      </tr>
      <tr>
          <td>來源幻覺（Source hallucination）</td>
          <td>清單項目未逐項核對來源</td>
      </tr>
      <tr>
          <td>工具化偏誤（Toolification）</td>
          <td>把新增工具當成預設答案</td>
      </tr>
      <tr>
          <td>家長主義悖論（Paternalism paradox）</td>
          <td>為保護使用者而過度限制使用者</td>
      </tr>
      <tr>
          <td>絆腳索疲乏（Tripwire fatigue）</td>
          <td>提醒太頻繁導致被忽略</td>
      </tr>
  </tbody>
</table>
<hr>
<p><strong>Last Updated</strong>: 2026-04-30
<strong>Version</strong>: 2.0.0 — 可攜案例庫模板。</p>
]]></content:encoded></item><item><title>WRAP Integration Patterns</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns/</guid><description>&lt;p>本目錄的責任是提供 WRAP 在任務系統、CLI、掛鉤（Hook）、規則庫與案例庫中的可攜整合模式。內容只描述通用整合語意，不依賴特定專案路徑或工具名稱。&lt;/p>
&lt;hr>
&lt;h2 id="依賴方向">依賴方向&lt;/h2>





&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">WRAP core skill -&amp;gt; integration patterns -&amp;gt; project-specific implementation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">通用原理 可攜模板 各專案自行落地&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>整合層可以引用 WRAP core skill，專案實作可以引用整合層。WRAP core skill 保持獨立，避免反向依賴任何專案實作。&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>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-triggers-alignment/" data-link-title="Triggers Alignment Pattern" data-link-desc="WRAP integration reference：讓文字規則、機器設定與自動提醒共享同一組觸發語意。">triggers-alignment&lt;/a>&lt;/td>
 &lt;td>觸發條件在文字規則、機器設定與自動提醒之間的同步模式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-simplified-three-questions/" data-link-title="Simplified Three Questions" data-link-desc="WRAP integration reference：任務啟動前保留 W/A/P 最低品質門檻的簡化三問。">simplified-three-questions&lt;/a>&lt;/td>
 &lt;td>任務啟動時的 W/A/P 三問模板&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-pseudo-widen-guard/" data-link-title="偽擴增選項防護（Pseudo Widen Guard）" data-link-desc="WRAP integration reference：辨識假選項與根因假設單一化的偽擴增選項（pseudo-Widen）防護。">pseudo-widen-guard&lt;/a>&lt;/td>
 &lt;td>偽擴增選項（pseudo-Widen）防護與假設層級多元性檢查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-source-verification/" data-link-title="Source Verification Pattern" data-link-desc="WRAP integration reference：清單類答案逐項對照來源、防止候選幻覺混入結論。">source-verification&lt;/a>&lt;/td>
 &lt;td>清單類答案的逐項來源核對流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-personalized-advice/" data-link-title="Personalized Advice Integration" data-link-desc="WRAP integration reference：把 Step 0 資料充足度閘門落到個人化建議對話。">personalized-advice&lt;/a>&lt;/td>
 &lt;td>個人化建議場景的 Step 0 落地方式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-rules-map/" data-link-title="Rules Map Pattern" data-link-desc="WRAP integration reference：說明 WRAP 與專案規則庫如何分工與互補。">rules-map&lt;/a>&lt;/td>
 &lt;td>規則庫與 WRAP 的分工模板&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-case-studies/" data-link-title="WRAP Case Study Pattern" data-link-desc="WRAP integration reference：把實戰事件轉成可重用決策教訓的案例庫模式。">case-studies&lt;/a>&lt;/td>
 &lt;td>案例庫的收錄欄位與抽象化方式&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="使用方式">使用方式&lt;/h2>
&lt;ol>
&lt;li>先讀 &lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/skill/" data-link-title="WRAP 決策框架 — SKILL 入口" data-link-desc="WRAP 決策框架的 SKILL 入口：錨點確認、資料充足度閘門、擴增選項、現實檢驗、機會成本、行前預想與絆腳索。">skill&lt;/a>，確認是否觸發 WRAP。&lt;/li>
&lt;li>需要把 WRAP 接到工具或流程時，讀本目錄的對應模板。&lt;/li>
&lt;li>在專案內建立自己的實作文件，填入實際路徑、指令、狀態欄位與 owner。&lt;/li>
&lt;li>保持核心 skill 與專案實作分離。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="可攜限制">可攜限制&lt;/h2>
&lt;p>本目錄保留為可攜模板，因此遵守以下限制：&lt;/p>
&lt;ul>
&lt;li>只使用 skill 內部相對連結與中性範例。&lt;/li>
&lt;li>只使用通用任務名稱，避免專案內部任務編號、批次代號、錯誤代碼或歷史編號。&lt;/li>
&lt;li>保持 CLI、掛鉤框架（Hook framework）與任務管理工具的供應商中立。&lt;/li>
&lt;li>範例使用中性名稱，例如 task、rule store、automation hook、case library。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;strong>Last Updated&lt;/strong>: 2026-04-30
&lt;strong>Version&lt;/strong>: 2.0.0 — 改為 portable integration patterns。&lt;/p></description><content:encoded><![CDATA[<p>本目錄的責任是提供 WRAP 在任務系統、CLI、掛鉤（Hook）、規則庫與案例庫中的可攜整合模式。內容只描述通用整合語意，不依賴特定專案路徑或工具名稱。</p>
<hr>
<h2 id="依賴方向">依賴方向</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">WRAP core skill  -&gt;  integration patterns  -&gt;  project-specific implementation
</span></span><span class="line"><span class="ln">2</span><span class="cl">通用原理             可攜模板                  各專案自行落地</span></span></code></pre></div><p>整合層可以引用 WRAP core skill，專案實作可以引用整合層。WRAP core skill 保持獨立，避免反向依賴任何專案實作。</p>
<hr>
<h2 id="檔案清單">檔案清單</h2>
<table>
  <thead>
      <tr>
          <th>檔案</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-triggers-alignment/" data-link-title="Triggers Alignment Pattern" data-link-desc="WRAP integration reference：讓文字規則、機器設定與自動提醒共享同一組觸發語意。">triggers-alignment</a></td>
          <td>觸發條件在文字規則、機器設定與自動提醒之間的同步模式</td>
      </tr>
      <tr>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-simplified-three-questions/" data-link-title="Simplified Three Questions" data-link-desc="WRAP integration reference：任務啟動前保留 W/A/P 最低品質門檻的簡化三問。">simplified-three-questions</a></td>
          <td>任務啟動時的 W/A/P 三問模板</td>
      </tr>
      <tr>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-pseudo-widen-guard/" data-link-title="偽擴增選項防護（Pseudo Widen Guard）" data-link-desc="WRAP integration reference：辨識假選項與根因假設單一化的偽擴增選項（pseudo-Widen）防護。">pseudo-widen-guard</a></td>
          <td>偽擴增選項（pseudo-Widen）防護與假設層級多元性檢查</td>
      </tr>
      <tr>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-source-verification/" data-link-title="Source Verification Pattern" data-link-desc="WRAP integration reference：清單類答案逐項對照來源、防止候選幻覺混入結論。">source-verification</a></td>
          <td>清單類答案的逐項來源核對流程</td>
      </tr>
      <tr>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-personalized-advice/" data-link-title="Personalized Advice Integration" data-link-desc="WRAP integration reference：把 Step 0 資料充足度閘門落到個人化建議對話。">personalized-advice</a></td>
          <td>個人化建議場景的 Step 0 落地方式</td>
      </tr>
      <tr>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-rules-map/" data-link-title="Rules Map Pattern" data-link-desc="WRAP integration reference：說明 WRAP 與專案規則庫如何分工與互補。">rules-map</a></td>
          <td>規則庫與 WRAP 的分工模板</td>
      </tr>
      <tr>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-case-studies/" data-link-title="WRAP Case Study Pattern" data-link-desc="WRAP integration reference：把實戰事件轉成可重用決策教訓的案例庫模式。">case-studies</a></td>
          <td>案例庫的收錄欄位與抽象化方式</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="使用方式">使用方式</h2>
<ol>
<li>先讀 <a href="/blog/skills/wrap-decision/skill/" data-link-title="WRAP 決策框架 — SKILL 入口" data-link-desc="WRAP 決策框架的 SKILL 入口：錨點確認、資料充足度閘門、擴增選項、現實檢驗、機會成本、行前預想與絆腳索。">skill</a>，確認是否觸發 WRAP。</li>
<li>需要把 WRAP 接到工具或流程時，讀本目錄的對應模板。</li>
<li>在專案內建立自己的實作文件，填入實際路徑、指令、狀態欄位與 owner。</li>
<li>保持核心 skill 與專案實作分離。</li>
</ol>
<hr>
<h2 id="可攜限制">可攜限制</h2>
<p>本目錄保留為可攜模板，因此遵守以下限制：</p>
<ul>
<li>只使用 skill 內部相對連結與中性範例。</li>
<li>只使用通用任務名稱，避免專案內部任務編號、批次代號、錯誤代碼或歷史編號。</li>
<li>保持 CLI、掛鉤框架（Hook framework）與任務管理工具的供應商中立。</li>
<li>範例使用中性名稱，例如 task、rule store、automation hook、case library。</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-30
<strong>Version</strong>: 2.0.0 — 改為 portable integration patterns。</p>
]]></content:encoded></item><item><title>WRAP 決策框架 — SKILL 入口</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/skill/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/skill/</guid><description>&lt;p>&lt;strong>框架結構&lt;/strong>：錨點確認 → Step 0（資料充足度閘門）→ W（擴增選項）→ R（現實檢驗）→ A（拉開距離）→ P（準備好犯錯）→ 絆腳索（持續監控）&lt;/p>
&lt;p>&lt;strong>核心理念&lt;/strong>：提醒決策者「你是有選擇的」，不替決策者做選擇。&lt;/p>
&lt;blockquote>
&lt;p>本 SKILL 為通用 WRAP 規則，獨立於任何專案框架。專案若需要掛鉤（Hook）、CLI 或任務系統整合，請在該專案內另建落地層文件。&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;th>模式&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>連續失敗&lt;/td>
 &lt;td>同一問題修改 2+ 次仍失敗&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>連續 2+ 個任務單位不在當前迭代目標&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>花 &amp;gt; 30 分鐘在非原定計畫的問題上&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;strong>根因深度反思&lt;/strong>&lt;/td>
 &lt;td>用戶質疑「分析太表層」「不夠深」、或需要把失敗歸因寫成可重用規則&lt;/td>
 &lt;td>&lt;strong>完整 WRAP 強制&lt;/strong>：先列 5+ 候選假設 + 現實檢驗（Reality Test） + 2 層深因，再用 W/A/P 檢驗&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>反思深度質疑&lt;/strong>&lt;/td>
 &lt;td>用戶要求「太表層」「再想想」「更深一層」「還有其他可能」「反向驗證」&lt;/td>
 &lt;td>&lt;strong>完整 WRAP 強制&lt;/strong>：先擴增假設，再做反向驗證，最後回到可執行結論&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>完整（重 P 階段回退計畫）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>利害關係人衝突&lt;/td>
 &lt;td>多方目標不一致、需平衡不同立場&lt;/td>
 &lt;td>完整（重 W 假選項偵測）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>個人化建議&lt;/strong>&lt;/td>
 &lt;td>使用者用「我」「我該」「推薦給我」，或話題涉及健康/運動/裝備/金錢/法律/醫療&lt;/td>
 &lt;td>&lt;strong>Step 0 強制&lt;/strong>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>CLI / 規則自動駕駛（autopilot）&lt;/strong>（決策路徑層 3.1）&lt;/td>
 &lt;td>CLI 撞錯後立即重試或猜變體（非查 &lt;code>--help&lt;/code> / 規則文件）&lt;/td>
 &lt;td>快速（強制查文件後再試）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>既有結論錨定（Anchor）&lt;/strong>（決策路徑層 3.2）&lt;/td>
 &lt;td>WRAP W 階段選項能一句話概括 / 全指向同一根因&lt;/td>
 &lt;td>完整（強制反向思考（Consider the Opposite） + 重新定義問題）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>規則失敗草率改規則&lt;/strong>（決策路徑層 3.3）&lt;/td>
 &lt;td>失敗第一反應「改規則」，未先重試 2 次&lt;/td>
 &lt;td>快速（先挖根因再決定改規則或改行為）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>多步驟成功率盲點&lt;/strong>（決策路徑層 3.4）&lt;/td>
 &lt;td>多步驟計畫中所有中間步驟都預測成功&lt;/td>
 &lt;td>快速（R 階段基本率 + 每步獨立驗證）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;blockquote>
&lt;p>上述 4 項決策路徑層因子可由各專案映射到自己的掛鉤（Hook）、CLI 或任務系統；本 skill 只保留通用判斷語意。&lt;/p>&lt;/blockquote>
&lt;p>&lt;strong>快速模式&lt;/strong>（5 分鐘）：錨點 + Step 0 + W + 基本率（R 核心）+ 機會成本（A 核心）+ 決定
&lt;strong>快速+模式&lt;/strong>：快速模式 + 強制 R 的基本率 / 反向驗證兩階段反思（分析任務最容易跳過事證直接下結論，故在快速基礎上補回 R 核心 + 一輪反向驗證）
&lt;strong>完整模式&lt;/strong>（15-30 分鐘）：全階段
&lt;strong>Step 0 強制&lt;/strong>：個人化建議場景，不論採用哪個模式，Step 0 為必經閘門&lt;/p>
&lt;blockquote>
&lt;p>各專案可依自身工作流建立機器可讀觸發條件、自動觸發機制、關鍵字清單對應，以及任務啟動階段的簡化三問（W/A/P 1-2 分鐘版）。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="錨點確認anchor-check">錨點確認（Anchor Check）&lt;/h2>
&lt;p>進入分析前先確立錨點。&lt;/p>
&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>「誰是我們的客戶？」&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;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>區分「改善工具」vs「低價值維護」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>「不做這個改善，會重複付出什麼代價？」&lt;/td>
 &lt;td>評估一次性投入 vs 持續回報&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>閘門判斷&lt;/strong>：&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">這個問題影響核心客戶 / 核心目標嗎？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ├─ 是 → 進入 Step 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> ├─ 否 → 這個流程改善影響決策品質或開發效率嗎？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> │ ├─ 是且持續回報 &amp;gt; 一次性投入 → 進入 Step 0
&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"> └─ 純粹救火 → 建任務單位 + 低優先級 → 回到核心任務&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>引用：英特普拉思特「病人才是我們的客戶」— 確立後，所有困難的決定都有了錨點。&lt;/p></description><content:encoded><![CDATA[<p><strong>框架結構</strong>：錨點確認 → Step 0（資料充足度閘門）→ W（擴增選項）→ R（現實檢驗）→ A（拉開距離）→ P（準備好犯錯）→ 絆腳索（持續監控）</p>
<p><strong>核心理念</strong>：提醒決策者「你是有選擇的」，不替決策者做選擇。</p>
<blockquote>
<p>本 SKILL 為通用 WRAP 規則，獨立於任何專案框架。專案若需要掛鉤（Hook）、CLI 或任務系統整合，請在該專案內另建落地層文件。</p></blockquote>
<hr>
<h2 id="觸發條件">觸發條件</h2>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>識別特徵</th>
          <th>模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>連續失敗</td>
          <td>同一問題修改 2+ 次仍失敗</td>
          <td>快速</td>
      </tr>
      <tr>
          <td>被困住</td>
          <td>表達「做不到」「沒辦法」「不支援」「禁止」「不可能」</td>
          <td>快速</td>
      </tr>
      <tr>
          <td>偏離核心</td>
          <td>連續 2+ 個任務單位不在當前迭代目標</td>
          <td>完整</td>
      </tr>
      <tr>
          <td>重大決策</td>
          <td>影響架構/流程/版本方向的非技術決策</td>
          <td>完整</td>
      </tr>
      <tr>
          <td>救火排擠</td>
          <td>花 &gt; 30 分鐘在非原定計畫的問題上</td>
          <td>快速</td>
      </tr>
      <tr>
          <td>分析任務</td>
          <td>根因調查、代理人失敗歸因、測試失敗檢討</td>
          <td>快速+（配合兩階段反思）</td>
      </tr>
      <tr>
          <td><strong>根因深度反思</strong></td>
          <td>用戶質疑「分析太表層」「不夠深」、或需要把失敗歸因寫成可重用規則</td>
          <td><strong>完整 WRAP 強制</strong>：先列 5+ 候選假設 + 現實檢驗（Reality Test） + 2 層深因，再用 W/A/P 檢驗</td>
      </tr>
      <tr>
          <td><strong>反思深度質疑</strong></td>
          <td>用戶要求「太表層」「再想想」「更深一層」「還有其他可能」「反向驗證」</td>
          <td><strong>完整 WRAP 強制</strong>：先擴增假設，再做反向驗證，最後回到可執行結論</td>
      </tr>
      <tr>
          <td>提案評估</td>
          <td>評估提案可行性</td>
          <td>完整</td>
      </tr>
      <tr>
          <td>不可逆 / 時間壓力</td>
          <td>一旦執行難以回退，或須在很短時間內定案</td>
          <td>完整（重 P 階段回退計畫）</td>
      </tr>
      <tr>
          <td>利害關係人衝突</td>
          <td>多方目標不一致、需平衡不同立場</td>
          <td>完整（重 W 假選項偵測）</td>
      </tr>
      <tr>
          <td><strong>個人化建議</strong></td>
          <td>使用者用「我」「我該」「推薦給我」，或話題涉及健康/運動/裝備/金錢/法律/醫療</td>
          <td><strong>Step 0 強制</strong></td>
      </tr>
      <tr>
          <td><strong>CLI / 規則自動駕駛（autopilot）</strong>（決策路徑層 3.1）</td>
          <td>CLI 撞錯後立即重試或猜變體（非查 <code>--help</code> / 規則文件）</td>
          <td>快速（強制查文件後再試）</td>
      </tr>
      <tr>
          <td><strong>既有結論錨定（Anchor）</strong>（決策路徑層 3.2）</td>
          <td>WRAP W 階段選項能一句話概括 / 全指向同一根因</td>
          <td>完整（強制反向思考（Consider the Opposite） + 重新定義問題）</td>
      </tr>
      <tr>
          <td><strong>規則失敗草率改規則</strong>（決策路徑層 3.3）</td>
          <td>失敗第一反應「改規則」，未先重試 2 次</td>
          <td>快速（先挖根因再決定改規則或改行為）</td>
      </tr>
      <tr>
          <td><strong>多步驟成功率盲點</strong>（決策路徑層 3.4）</td>
          <td>多步驟計畫中所有中間步驟都預測成功</td>
          <td>快速（R 階段基本率 + 每步獨立驗證）</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>上述 4 項決策路徑層因子可由各專案映射到自己的掛鉤（Hook）、CLI 或任務系統；本 skill 只保留通用判斷語意。</p></blockquote>
<p><strong>快速模式</strong>（5 分鐘）：錨點 + Step 0 + W + 基本率（R 核心）+ 機會成本（A 核心）+ 決定
<strong>快速+模式</strong>：快速模式 + 強制 R 的基本率 / 反向驗證兩階段反思（分析任務最容易跳過事證直接下結論，故在快速基礎上補回 R 核心 + 一輪反向驗證）
<strong>完整模式</strong>（15-30 分鐘）：全階段
<strong>Step 0 強制</strong>：個人化建議場景，不論採用哪個模式，Step 0 為必經閘門</p>
<blockquote>
<p>各專案可依自身工作流建立機器可讀觸發條件、自動觸發機制、關鍵字清單對應，以及任務啟動階段的簡化三問（W/A/P 1-2 分鐘版）。</p></blockquote>
<hr>
<h2 id="錨點確認anchor-check">錨點確認（Anchor Check）</h2>
<p>進入分析前先確立錨點。</p>
<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>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>目的</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「這個流程改善影響決策品質或開發效率嗎？」</td>
          <td>區分「改善工具」vs「低價值維護」</td>
      </tr>
      <tr>
          <td>「不做這個改善，會重複付出什麼代價？」</td>
          <td>評估一次性投入 vs 持續回報</td>
      </tr>
  </tbody>
</table>
<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">    ├─ 是 → 進入 Step 0
</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">    │        ├─ 是且持續回報 &gt; 一次性投入 → 進入 Step 0
</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">    └─ 純粹救火 → 建任務單位 + 低優先級 → 回到核心任務</span></span></code></pre></div><blockquote>
<p>引用：英特普拉思特「病人才是我們的客戶」— 確立後，所有困難的決定都有了錨點。</p></blockquote>
<hr>
<h2 id="step-0-資料充足度閘門data-sufficiency-gate">Step 0. 資料充足度閘門（Data Sufficiency Gate）</h2>
<p><strong>核心問句</strong>：「我手上的資訊夠做這個決策嗎？還是我正在用假設替代資料？」</p>
<h3 id="模式辨識">模式辨識</h3>
<table>
  <thead>
      <tr>
          <th>模式</th>
          <th>特徵</th>
          <th>答案來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>資訊查詢</td>
          <td>問的是客觀知識（「X 和 Y 差別」「怎麼運作」）</td>
          <td>公開資料即可</td>
      </tr>
      <tr>
          <td>決策諮詢</td>
          <td>問的是「我該怎麼選/做/買」</td>
          <td><strong>必須基於當事人條件</strong></td>
      </tr>
  </tbody>
</table>
<p>使用者說「我是 X」、「推薦給我」、「我該買哪個」 → 決策諮詢。決策諮詢以個案資料為基準，群體平均值只作為背景參考。</p>
<h3 id="判定流程">判定流程</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">Step 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">    ├─ 檢查 1：我知道當事人是誰嗎？（年齡/性別/身體/財務條件）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    ├─ 檢查 2：我知道當事人的目標嗎？
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    ├─ 檢查 3：我知道當事人的限制嗎？（預算/時間/禁忌）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    ├─ 檢查 4：我知道當事人的環境嗎？（地區/可用資源）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    ├─ 檢查 5：我在用「平均值假設」替代「個人資料」嗎？
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    └─ 檢查 6：如果假設錯誤，後果會嚴重嗎？
</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">    ├─ 資料充足 → 進入 W 階段
</span></span><span class="line"><span class="ln">13</span><span class="cl">    ├─ 資料不足 + 低風險 → 進入 W，但標記假設
</span></span><span class="line"><span class="ln">14</span><span class="cl">    ├─ 資料不足 + 中風險 → 暫停，漸進式問 2-3 個關鍵變數
</span></span><span class="line"><span class="ln">15</span><span class="cl">    └─ 資料不足 + 高風險 → 強制完整問卷 + 建議諮詢專業人士</span></span></code></pre></div><h3 id="為什麼-step-0-必須在-w-之前">為什麼 Step 0 必須在 W 之前</h3>
<p>W 階段「擴增選項（Widen Options）」是「擴增選項空間」，但選項的意義取決於<strong>當事人條件</strong>。如果不先確認資料充足度：</p>
<table>
  <thead>
      <tr>
          <th>表面現象</th>
          <th>實際狀況</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>列出 A/B/C/D 四個選項（看似多元）</td>
          <td>四個選項都基於「用戶是平均值」的假設</td>
      </tr>
      <tr>
          <td>對選項做現實檢驗（Reality Test）</td>
          <td>只是驗證「對平均值用戶是否可行」，非「對此用戶可行」</td>
      </tr>
      <tr>
          <td>Attain Distance 考量機會成本</td>
          <td>比較的是錯誤假設下的成本</td>
      </tr>
      <tr>
          <td>Prepare to be Wrong</td>
          <td>預想的失敗原因都是技術性，忽略「假設錯誤」這個最大風險</td>
      </tr>
  </tbody>
</table>
<p><strong>結論</strong>：Step 0 缺失會讓整個 WRAP 流程變成「精美的錯誤分析」。</p>
<h3 id="反模式偵測">反模式偵測</h3>
<table>
  <thead>
      <tr>
          <th>警告信號</th>
          <th>含義</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>擴增選項（Widen Options）時寫出「適合的用戶選 A，其他用戶選 B」</td>
          <td>已承認用戶差異會影響選擇，卻未先確認用戶是哪種</td>
      </tr>
      <tr>
          <td>選項描述含「通常」「一般來說」「大多數人」</td>
          <td>用群體敘述替代個體判定</td>
      </tr>
      <tr>
          <td>執行現實檢驗（Reality Test）時發現「數據來源是群體統計」</td>
          <td>這屬於 Step 0 應提前檢出的問題</td>
      </tr>
      <tr>
          <td>Attain Distance 的機會成本計算需假設用戶偏好</td>
          <td>偏好是 Step 0 資料，不該在 A 階段假設</td>
      </tr>
  </tbody>
</table>
<p><strong>失敗模式</strong>：Step 0 最容易被跳過是因為反問當事人打斷對話流暢度。流暢度與準確度的取捨應交由當事人決定。</p>
<blockquote>
<p>各專案可建立個人化建議的三層機制（識別 / 分級 / 誠實）。</p></blockquote>
<hr>
<h2 id="w--擴增選項widen-your-options">W — 擴增選項（Widen Your Options）</h2>
<p><strong>核心問句</strong>：「還有什麼其他方式可以達成目標？」</p>
<h3 id="爬梯子法由近到遠">爬梯子法（由近到遠）</h3>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>搜尋範圍</th>
          <th>做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0. 身邊</td>
          <td>當前專案既有程式碼/模組</td>
          <td>搜尋既有 API</td>
      </tr>
      <tr>
          <td>1. 同層</td>
          <td>當前專案其他模組的做法</td>
          <td>檢查類似功能的實作</td>
      </tr>
      <tr>
          <td>2. 同領域</td>
          <td>社群（GitHub Issues、文件）</td>
          <td>網路搜尋</td>
      </tr>
      <tr>
          <td>3. 跨領域</td>
          <td>其他工具/框架的類比方案</td>
          <td>AI 內建知識調用</td>
      </tr>
  </tbody>
</table>
<p>每層找到可行方案就停止，找不到才往上爬。</p>
<h3 id="品質檢查">品質檢查</h3>
<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>可以 → 選項多元性不足</td>
      </tr>
      <tr>
          <td><strong>方案性質三類涵蓋</strong></td>
          <td>候選方案是否涵蓋 <strong>新增工具 / 改造既有 / 零工具或純文件</strong> 三類？</td>
          <td>全屬「新增工具」→ LLM 工具化偏誤（toolification bias），強制補「改造既有」與「零工具」選項</td>
      </tr>
      <tr>
          <td><strong>方案關聯性檢查</strong></td>
          <td>任兩方案是否可退化為同一方案或既有機制？</td>
          <td>可退化 → 實質選項數量要重算（假多樣性）</td>
      </tr>
      <tr>
          <td>反向思考（Consider the Opposite）</td>
          <td>如果我相信的正好相反，會怎樣？真正該問的問題是什麼？</td>
          <td>能提出有力相反論述 → 重新定義問題後從 W 重跑</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>反向思考（Consider the Opposite）是最容易被跳過但最重要的檢查 — 也是防止「在錯誤問題框架下擴增選項」的最後防線。詳細操作與範例見 <a href="/blog/skills/wrap-decision/detailed-techniques/" data-link-title="WRAP 詳細技巧說明" data-link-desc="WRAP reference：每個階段的詳細操作技巧、範例與決策品質檢查。">detailed-techniques</a>。</p></blockquote>
<blockquote>
<p><strong>方案性質三類涵蓋</strong>與<strong>方案關聯性檢查</strong>用來抵抗 LLM 的工具化偏誤（toolification bias）：LLM 在不少情境傾向把「新增工具」當答案，較少提「改造既有機制」與「零工具」選項。即使補上多個方案，若方案間強關聯也可能退化為 1 個實質選項。</p></blockquote>
<h3 id="假設層級多元性真正的擴增選項widen">假設層級多元性（真正的擴增選項，Widen）</h3>
<h4 id="選項必須在假設根因層級多元而不只是實作手段層級多元">選項必須在「假設根因」層級多元，而不只是「實作手段」層級多元</h4>
<p>擴增選項（Widen）列出多個方案 ≠ 真正擴增了選項空間。若所有方案都接受同一個未驗證的根因假設，仍是偽擴增選項（pseudo-Widen） — 方案脫靶率會集中分佈（要嘛全中、要嘛全脫）。</p>
<p><strong>最低操作</strong>：列方案前先寫出「我假設問題是 X 造成的」，再質疑 X 是否為真根因。</p>
<blockquote>
<p>分析類任務（根因調查、設計決策）建議完整執行三層質疑、現實檢驗（Reality Test）閘門與警告信號檢查。</p></blockquote>
<h3 id="多輪迭代查詢深度議題建議">多輪迭代查詢（深度議題建議）</h3>
<p>對複雜議題（如規則設計、提案評估），W 階段不應一次性完成擴增，採四輪迭代結構：</p>
<table>
  <thead>
      <tr>
          <th>輪次</th>
          <th>名稱</th>
          <th>目的</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>發散型 (Divergent)</td>
          <td>廣泛關鍵字鋪面建立問題地圖</td>
      </tr>
      <tr>
          <td>2</td>
          <td>具體化 (Concrete)</td>
          <td>將抽象主題轉為具體案例</td>
      </tr>
      <tr>
          <td>3</td>
          <td>精準化 (Precise)</td>
          <td>從前兩輪提煉精準關鍵字深挖</td>
      </tr>
      <tr>
          <td>4</td>
          <td>反向驗證 (Inverse)</td>
          <td>對結論找反例 / 批評 / 反駁</td>
      </tr>
  </tbody>
</table>
<p>進入下一輪訊號（至少 3 條）、邊界條件、實證統計詳見 <a href="/blog/skills/wrap-decision/iterative-research/" data-link-title="多輪迭代查詢方法論" data-link-desc="WRAP reference：用發散、具體化、精準化、反向驗證四輪結構支撐深度查詢與反向驗證。">iterative-research</a>。</p>
<hr>
<h2 id="r--現實檢驗reality-test-your-assumptions">R — 現實檢驗（Reality-Test Your Assumptions）</h2>
<p><strong>核心問句</strong>：「需要什麼事證才能證明這個方法可行？」</p>
<h3 id="基本率--預測">基本率 &gt; 預測</h3>
<table>
  <thead>
      <tr>
          <th>壞問題（預測）</th>
          <th>好問題（基本率）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「這樣改會成功嗎？」</td>
          <td>「這類問題的常見解法有哪些？成功率？」</td>
      </tr>
      <tr>
          <td>「我目前相信的根因會成功嗎？」</td>
          <td>「這類症狀的常見根因分佈是什麼？」</td>
      </tr>
      <tr>
          <td>「代理人會完成嗎？」</td>
          <td>「這類任務耗盡資源的常見閾值是什麼？」</td>
      </tr>
  </tbody>
</table>
<h3 id="大範圍觀照zoom-out-近距離檢視zoom-in">大範圍觀照（Zoom Out）+ 近距離檢視（Zoom In）</h3>
<table>
  <thead>
      <tr>
          <th>方法</th>
          <th>做法</th>
          <th>產出</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>大範圍觀照（Zoom Out）</td>
          <td>搜尋基本率：多少人遇到？常見解法？</td>
          <td>統計性判斷</td>
      </tr>
      <tr>
          <td>近距離檢視（Zoom In）</td>
          <td>讀具體案例內文、實際變更內容</td>
          <td>直覺性判斷</td>
      </tr>
  </tbody>
</table>
<p>兩者缺一不可：基本率建立基準，近距離檢視觸動直覺。</p>
<p><strong>大範圍觀照（Zoom Out）前置確認</strong>（搜尋範圍校準）：</p>
<blockquote>
<p>搜尋前問自己：「我搜尋的是<strong>問題本身</strong>的基本率，還是<strong>我預設解法</strong>的基本率？」</p>
<ul>
<li>搜尋「tool call 閾值校準」→ 預設解法的基本率（狹窄）</li>
<li>搜尋「為什麼要拆分任務」→ 問題本身的基本率（廣闊）</li>
</ul></blockquote>
<h3 id="清單類答案的來源核對">清單類答案的來源核對</h3>
<p><strong>LLM 列清單時最容易產生幻覺</strong>（會「補齊」看起來合理的項目）。現實檢驗（Reality Test）對清單類答案必須執行<strong>逐項對 source 核對</strong>，禁止整批信任。</p>
<p><strong>最低規則</strong>：</p>
<ul>
<li>找到該領域的權威 source（官方 docs、本機 spec、Context7）</li>
<li>清單每一項對照 source；找不到 → 標記為候選幻覺</li>
<li>單項細節（schema、欄位語意）比整體清單可信</li>
</ul>
<blockquote>
<p>完整防護重點是幻覺模式分類、逐項核對流程與反模式識別；各專案可另建來源清單與核對模板。</p></blockquote>
<h3 id="試水溫ooch">試水溫（Ooch）</h3>
<p>對每個選項問：「能否用最小成本驗證？5 分鐘內能得到初步答案嗎？」</p>
<ul>
<li>能 → 先試水溫再決策</li>
<li>驗證成本高 → 進入完整分析</li>
</ul>
<h3 id="最強版本論證steelman">最強版本論證（Steelman）</h3>
<p>選定方案後、執行前：</p>
<ol>
<li>「用最有力的方式陳述被放棄選項的優點」</li>
<li>「列出選定方案的三個缺點」</li>
</ol>
<p>做得到 → 決策品質足夠；做不到 → 回到 R 階段補充。</p>
<h3 id="反向驗證範本深度議題建議">反向驗證範本（深度議題建議）</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">| [結論] | [批評 / 反例 / 反駁 / 限制] |</span></span></code></pre></div><p>至少涵蓋 4-8 種反向方向類型（學術批評 / 反方論點 / 失效情境 / 統計限制 / 文化限制 / 家長主義（paternalism）警示 / 取捨揭露（trade-off） / 自我參照悖論）。</p>
<p>詳見 <a href="/blog/skills/wrap-decision/iterative-research/" data-link-title="多輪迭代查詢方法論" data-link-desc="WRAP reference：用發散、具體化、精準化、反向驗證四輪結構支撐深度查詢與反向驗證。">iterative-research</a> 「反向驗證實踐範本」章節。</p>
<hr>
<h2 id="a--拉開距離attain-distance">A — 拉開距離（Attain Distance）</h2>
<p><strong>核心問句</strong>：「投入這個問題的時間，會擠壓哪個更重要的目標？」</p>
<h3 id="前置強制檢查">前置強制檢查</h3>
<p>進入 A 前必完成以下 4 項檢查，任一觸發即回 W 階段重擴增。</p>
<table>
  <thead>
      <tr>
          <th>檢查項</th>
          <th>問題</th>
          <th>觸發條件</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>框架概括測試</td>
          <td>能否用一句話概括所有候選方案？</td>
          <td>「能」→ 選項多元性不足，回 W</td>
      </tr>
      <tr>
          <td>反面框架識別</td>
          <td>問題的反面框架是什麼？（例：「如何擋 X」的反面是「為何會有 X」）</td>
          <td>未列出反面框架即進 A → 強制停下列出</td>
      </tr>
      <tr>
          <td>相反論述檢視</td>
          <td>反向思考（Consider the Opposite）：若我相信的正好相反會怎樣？真正該問的問題是什麼？</td>
          <td>能提出有力相反論述 → 重新定義問題後從 W 重跑</td>
      </tr>
      <tr>
          <td><strong>工具選擇檢查（Tool selection check）</strong></td>
          <td>物化 / 步驟數 / 目的地 / 白名單 4 問（見下）</td>
          <td>任一問暴露繞路 → 回 W 換 1 步工具</td>
      </tr>
  </tbody>
</table>
<p><strong>工具選擇檢查（Tool selection check）觸發條件</strong>：選工具（tool）前 + 預估步驟數 &gt; 2 + 涉及 Write/Bash 組合（例如 Write 暫存檔再 Read 再 Bash 寫入）。</p>
<p><strong>Tool selection 4 問</strong>：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>核心</th>
          <th>範例（反模式 → 正確）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Q1 物化檢查</td>
          <td>我是否把「解決方案」物化為「工具」（「用 Write 建檔」），而非解決「問題」（「把文字寫進任務紀錄」）?</td>
          <td>「Write /tmp/ctx.md 再 Read 再 bash append」（物化）→「heredoc append」（解決問題）</td>
      </tr>
      <tr>
          <td>Q2 步驟數檢查</td>
          <td>選擇路徑的實際步驟數是 N? 有無 1 步路徑被忽略?</td>
          <td>3 步（Write→Read→Bash）→ 1 步（heredoc Bash 或 Edit 任務紀錄）</td>
      </tr>
      <tr>
          <td>Q3 目的地檢查</td>
          <td>工具產出目的地是 CLI/檔案系統? 匹配需求嗎?</td>
          <td>目的是寫進任務紀錄 → 首選 Edit / heredoc CLI，避免多餘中介檔</td>
      </tr>
      <tr>
          <td>Q4 白名單檢查</td>
          <td>目前工具是否在「低摩擦首選」名單外（偏好 heredoc Bash / Edit 直接編輯，次選 Write 類）?</td>
          <td>長文寫入任務紀錄首選 Edit 或 <code>cat &lt;&lt;'EOF'</code> heredoc</td>
      </tr>
  </tbody>
</table>
<p><strong>違反防護的代價</strong>：A 階段方案比較表面周詳，實際全指向同類型解法；或單步最優化但總步驟盲，把多步繞路誤判為簡單方案。</p>
<blockquote>
<p>完整 4 問操作細節見 <a href="/blog/skills/wrap-decision/detailed-techniques/" data-link-title="WRAP 詳細技巧說明" data-link-desc="WRAP reference：每個階段的詳細操作技巧、範例與決策品質檢查。">detailed-techniques</a> 「工具選擇檢查（Tool selection check）詳細技巧」章節。</p></blockquote>
<h3 id="機會成本顯性化">機會成本顯性化</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">選項 A：[做法]
</span></span><span class="line"><span class="ln">2</span><span class="cl">  機會成本：花 X 時間，這段時間可以 [替代用途]
</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></code></pre></div><blockquote>
<p>引用：DVD 實驗 — 只加上「不買，留下 14.99 美元買別的」，不買率從 25% 升到 45%。顯性化機會成本能改變決策。</p></blockquote>
<h3 id="101010-規則">10/10/10 規則</h3>
<table>
  <thead>
      <tr>
          <th>時間</th>
          <th>問</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>10 分鐘後</td>
          <td>這個決定當下的感受</td>
      </tr>
      <tr>
          <td>10 個月後</td>
          <td>這個決定對使用者/專案的影響</td>
      </tr>
      <tr>
          <td>10 年後</td>
          <td>這個決定對整體生態/信任度的影響</td>
      </tr>
  </tbody>
</table>
<h3 id="核心優先事項對齊">核心優先事項對齊</h3>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>目的</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「這個決策服務於哪個優先事項？」</td>
          <td>對齊</td>
      </tr>
      <tr>
          <td>「如果優先事項衝突，哪個排第一？」</td>
          <td>排序</td>
      </tr>
      <tr>
          <td>「投入這問題的時間，擠壓哪個更重要的目標？」</td>
          <td>機會成本</td>
      </tr>
  </tbody>
</table>
<p>排序確定後，低優先事項的決策自動有答案 — 記錄延後，不投入時間。</p>
<h3 id="提案系統對接">提案系統對接</h3>
<p>使用「安全停放區」承接暫緩想法：想法被記錄，並明確標示目前聚焦的工作。</p>
<table>
  <thead>
      <tr>
          <th>系統</th>
          <th>角色</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>提案暫存區</td>
          <td>安全停放區 — 記錄想法，不代表要做</td>
      </tr>
      <tr>
          <td>任務追蹤系統（pending）</td>
          <td>承諾清單 — 已決定要做，排入排程</td>
      </tr>
      <tr>
          <td>迭代目標文件</td>
          <td>核心優先事項 — 當前迭代的目標</td>
      </tr>
  </tbody>
</table>
<h3 id="悖論識別檢查清單規則--skill--掛鉤hook設計時必檢">悖論識別檢查清單（規則 / Skill / 掛鉤（Hook）設計時必檢）</h3>
<p>設計規則 / 流程 / 系統時，必對 5 條檢查清單逐項自檢：</p>
<ul>
<li><input disabled="" type="checkbox"> 此規則是否本身違反它要保護的價值？</li>
<li><input disabled="" type="checkbox"> 此規則是否通過善意家長主義（benevolent paternalism）4 條件測試？（實質傷害 / 介入有效 / 利益大於風險 / 最小限制）</li>
<li><input disabled="" type="checkbox"> 此規則的「正確示範」是否本身違反它要禁止的模式？</li>
<li><input disabled="" type="checkbox"> 此規則的設計者立場是否被透明化？</li>
<li><input disabled="" type="checkbox"> 此規則是否預留「用戶可覆蓋」機制？</li>
</ul>
<p>詳見 <a href="/blog/skills/wrap-decision/anti-paternalism/" data-link-title="悖論識別與自我暴露偏好" data-link-desc="WRAP reference：規則設計中的家長主義（paternalism）悖論檢查、自我參照風險、偏好透明化與反操控防護。">anti-paternalism</a>（含規則設計中的家長主義（paternalism）悖論案例）。</p>
<hr>
<h2 id="p--準備好犯錯prepare-to-be-wrong">P — 準備好犯錯（Prepare to Be Wrong）</h2>
<p><strong>核心問句</strong>：「假設這個方案失敗了，最可能的原因是什麼？」</p>
<p>決策確定後、執行前，完成四項檢查：</p>
<table>
  <thead>
      <tr>
          <th>檢查</th>
          <th>問題</th>
          <th>判斷</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>行前預想（Premortem）</td>
          <td>假設 12 小時後失敗了，列出 3 個最可能原因</td>
          <td>任一原因可能性 &gt; 50% → 重新評估</td>
      </tr>
      <tr>
          <td>未來區間</td>
          <td>最好的結果？最壞的結果？</td>
          <td>兩端都能接受嗎？</td>
      </tr>
      <tr>
          <td>安全係數</td>
          <td>預估時間/資源 x 1.3-1.5（經驗起點、依任務調整）</td>
          <td>緩衝夠嗎？</td>
      </tr>
      <tr>
          <td>回退計畫</td>
          <td>失敗怎麼回退？回退成本？</td>
          <td>回退成本 &gt; 收益 → 重新評估</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>Gary Klein 方法：先假設計畫失敗了，然後問「是什麼殺了它？」比問「可能會失敗嗎？」多產出 25% 的洞察（且更具體）。</p></blockquote>
<h3 id="自我暴露偏好實踐提供建議時必行">自我暴露偏好實踐（提供建議時必行）</h3>
<p>提供建議時必暴露偏好、推理鏈、盲點，不偽裝中立：</p>
<table>
  <thead>
      <tr>
          <th>實踐</th>
          <th>禁止</th>
          <th>正確</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>暴露偏好</td>
          <td>假裝中立提問</td>
          <td>「我傾向 X，理由 Y」</td>
      </tr>
      <tr>
          <td>暴露推理鏈</td>
          <td>只給結論</td>
          <td>列出推理步驟讓用戶可追溯</td>
      </tr>
      <tr>
          <td>暴露盲點</td>
          <td>假裝完整考慮</td>
          <td>「我可能漏掉的角度有 Z」</td>
      </tr>
      <tr>
          <td>標記偏誤</td>
          <td>標成推薦（Recommended）</td>
          <td>改為「我目前的猜測」或不標</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>Voss 自陳：「one person&rsquo;s influence is another person&rsquo;s manipulation」——影響力的本質取決於是否被透明化。推薦標記（Recommended）已被學術界（DarkBench）列為 LLM 暗黑模式（dark pattern）。</p></blockquote>
<p>詳見 <a href="/blog/skills/wrap-decision/anti-paternalism/" data-link-title="悖論識別與自我暴露偏好" data-link-desc="WRAP reference：規則設計中的家長主義（paternalism）悖論檢查、自我參照風險、偏好透明化與反操控防護。">anti-paternalism</a> 「自我暴露偏好實踐」章節。</p>
<hr>
<h2 id="絆腳索tripwire">絆腳索（Tripwire）</h2>
<p><strong>核心理念</strong>：絆腳索不保證做出正確決定，但讓你意識到「是該做決定的時候了」。</p>
<h3 id="絆腳索類型">絆腳索類型</h3>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>觸發條件</th>
          <th>動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>期限型</strong></td>
          <td>非核心問題花 &gt; 15 分鐘</td>
          <td>建任務延後，回到核心</td>
      </tr>
      <tr>
          <td><strong>失敗型</strong></td>
          <td>同一修改連續 2 次失敗</td>
          <td>搜尋社群或換方向</td>
      </tr>
      <tr>
          <td><strong>偏離型</strong></td>
          <td>連續 2+ 個任務不在當前迭代目標</td>
          <td>回到核心任務</td>
      </tr>
      <tr>
          <td><strong>回退型</strong></td>
          <td>已回退過一次</td>
          <td>完全停止，換方向</td>
      </tr>
      <tr>
          <td><strong>嘗試型</strong></td>
          <td>同一問題修改嘗試 2 次</td>
          <td>向外求解</td>
      </tr>
      <tr>
          <td><strong>資料充足度</strong></td>
          <td>進入決策諮詢前</td>
          <td>強制 Step 0 閘門</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>完整類型（含正面絆腳索 — 捕捉意外成功）、命名效應、切割機制見 <a href="/blog/skills/wrap-decision/tripwire-catalog/" data-link-title="絆腳索類型目錄" data-link-desc="WRAP reference：期限型、失敗型、偏離型、回退型、嘗試型、資料充足度與正面絆腳索的監控目錄。">tripwire-catalog</a>。</p></blockquote>
<h3 id="階段間切割點">階段間切割點</h3>
<p>WRAP 每階段之間是切割點 — 強迫問「是否繼續」：</p>
<ul>
<li>Step 0 完成 → 「資料夠了嗎？還是需要先問？」</li>
<li>W 完成 → 「選項品質夠嗎？」</li>
<li>R 完成 → 「證據支持哪個選項？」</li>
<li>A 完成 → 「這符合核心優先事項嗎？」</li>
<li>P 完成 → 「行前預想的風險可接受嗎？」</li>
</ul>
<hr>
<h2 id="與其他決策類-skill-的關係">與其他決策類 Skill 的關係</h2>
<table>
  <thead>
      <tr>
          <th>Skill 類型</th>
          <th>分工</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>技術方案比較（3+ 方案比較）</td>
          <td>方案比較用專用 skill；認知偏誤防護用 WRAP</td>
      </tr>
      <tr>
          <td>Bug 證據驅動修復</td>
          <td>明確 bug 用證據驅動修復 skill；被困住/連續失敗用 WRAP</td>
      </tr>
      <tr>
          <td>任務認知負擔評估</td>
          <td>任務拆分用專用 skill；決策品質用 WRAP</td>
      </tr>
      <tr>
          <td>決策格式模板（5W1H）</td>
          <td>格式模板負責「怎麼寫」；WRAP 負責「怎麼想」</td>
      </tr>
      <tr>
          <td>學習捕捉</td>
          <td>WRAP 的正面絆腳索串接學習捕捉 skill</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="參考文件">參考文件</h2>
<h3 id="通用可跨專案複用">通用（可跨專案複用）</h3>
<table>
  <thead>
      <tr>
          <th>文件</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/skills/wrap-decision/detailed-techniques/" data-link-title="WRAP 詳細技巧說明" data-link-desc="WRAP reference：每個階段的詳細操作技巧、範例與決策品質檢查。">detailed-techniques</a></td>
          <td>每階段的詳細技巧、範例、書中引用（DVD 實驗、努金調解、季辛吉陷阱、Gary Klein 方法）</td>
      </tr>
      <tr>
          <td><a href="/blog/skills/wrap-decision/pm-checklist/" data-link-title="決策者快速參考清單（PM Checklist）" data-link-desc="WRAP reference：快速模式、完整模式與決策品質自測清單。">pm-checklist</a></td>
          <td>快速模式 + 完整模式檢查清單 + 決策品質自測</td>
      </tr>
      <tr>
          <td><a href="/blog/skills/wrap-decision/tripwire-catalog/" data-link-title="絆腳索類型目錄" data-link-desc="WRAP reference：期限型、失敗型、偏離型、回退型、嘗試型、資料充足度與正面絆腳索的監控目錄。">tripwire-catalog</a></td>
          <td>絆腳索類型完整目錄、自動駕駛失敗模式、切割機制、命名效應</td>
      </tr>
      <tr>
          <td><a href="/blog/skills/wrap-decision/iterative-research/" data-link-title="多輪迭代查詢方法論" data-link-desc="WRAP reference：用發散、具體化、精準化、反向驗證四輪結構支撐深度查詢與反向驗證。">iterative-research</a></td>
          <td>多輪迭代查詢方法論（4 輪結構：發散 → 具體化 → 精準化 → 反向驗證 + 進入下一輪訊號 + 反向驗證 8 種類型範本）</td>
      </tr>
      <tr>
          <td><a href="/blog/skills/wrap-decision/anti-paternalism/" data-link-title="悖論識別與自我暴露偏好" data-link-desc="WRAP reference：規則設計中的家長主義（paternalism）悖論檢查、自我參照風險、偏好透明化與反操控防護。">anti-paternalism</a></td>
          <td>悖論識別檢查清單 + 自我暴露偏好實踐（善意家長主義（benevolent paternalism）4 條件測試、自我參照悖論識別、推薦標記（Recommended）是暗黑模式（dark pattern））</td>
      </tr>
      <tr>
          <td><a href="/blog/skills/wrap-decision/claim-quick-wrap/" data-link-title="任務啟動快速 WRAP（Claim Quick WRAP）" data-link-desc="WRAP reference：任務啟動前用 1 到 2 分鐘檢查選項、機會成本與失敗防護的快速協議。">claim-quick-wrap</a></td>
          <td>任務啟動的簡化三問（W/A/P 1-2 分鐘版）、快速模式進一步壓縮版</td>
      </tr>
  </tbody>
</table>
<h3 id="專案整合落地層">專案整合（落地層）</h3>
<p>需要把 WRAP 接到具體掛鉤（Hook）/ CLI / 任務系統時讀。各檔為「通用語意 → 專案實作」的對應範本，複用到新專案時各自改寫。</p>
<table>
  <thead>
      <tr>
          <th>文件</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/skills/wrap-decision/integration-patterns/" data-link-title="WRAP Integration Patterns" data-link-desc="WRAP reference：把 WRAP 整合進任務系統、CLI、掛鉤（Hook）、規則庫與案例庫的可攜模式總覽。">integration-patterns</a></td>
          <td>落地層入口與路由：依賴方向（core → pattern → 專案）、8 個整合範本的索引</td>
      </tr>
  </tbody>
</table>
<hr>
<p><strong>Last Updated</strong>: 2026-04-28
<strong>Version</strong>: 2.4.0 — 多輪 review 修正：參考文件表補 integration-patterns 路由 + claim-quick-wrap（原 orphan）；觸發條件補「不可逆 / 時間壓力」「利害關係人衝突」+ 定義「快速+」模式；階段標題去除錯亂編號（0. / 5.）；LLM 工具化偏誤改條件式語氣；安全係數標經驗值。
<strong>Version</strong>: 2.3.0 — 觸發條件新增 4 項決策路徑層干擾（CLI 自動駕駛（autopilot） / 既有結論錨定（Anchor） / 草率改規則 / 多步驟成功率盲點）；既有觸發條件不變動（向後相容）。
<strong>Version</strong>: 2.2.0 — 觸發條件新增反思深度質疑（reflection_depth_challenge）說明，含與被困住語意的差異。
<strong>Version</strong>: 2.1.0 — 新增多輪迭代查詢方法論（W）+ 反向驗證範本（R）+ 悖論識別檢查清單（A）+ 自我暴露偏好實踐（P）+ 2 個新 references（iterative-research / anti-paternalism）。
<strong>Source</strong>: 《零偏見決斷法》(Decisive) — Chip Heath &amp; Dan Heath</p>
]]></content:encoded></item><item><title>WRAP 決策框架 — 認知偏誤防護與決策品質</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/</guid><description>&lt;h2 id="這個資料夾是什麼">這個資料夾是什麼&lt;/h2>
&lt;p>&lt;code>wrap-decision&lt;/code> 是一套 WRAP 決策框架 skill，原生位置在 &lt;a href="https://github.com/tarrragon/blog/tree/main/.claude/skills/wrap-decision">&lt;code>.claude/skills/wrap-decision/&lt;/code>&lt;/a> 供 Claude runtime 呼叫；這份是&lt;strong>同內容的文章版本&lt;/strong>，讓人類讀者也能直接在 blog 閱讀。&lt;/p>
&lt;p>核心是提醒決策者「你是有選擇的」。它把決策拆成錨點確認、Step 0 資料充足度閘門、W 擴增選項、R 現實檢驗、A 拉開距離、P 準備好犯錯，以及絆腳索（Tripwire）監控。&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/wrap-decision/skill/" data-link-title="WRAP 決策框架 — SKILL 入口" data-link-desc="WRAP 決策框架的 SKILL 入口：錨點確認、資料充足度閘門、擴增選項、現實檢驗、機會成本、行前預想與絆腳索。">SKILL 入口&lt;/a>&lt;/td>
 &lt;td>理解 WRAP 主流程、觸發條件與決策檢查順序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/pm-checklist/" data-link-title="決策者快速參考清單（PM Checklist）" data-link-desc="WRAP reference：快速模式、完整模式與決策品質自測清單。">決策者快速參考清單（PM Checklist）&lt;/a>&lt;/td>
 &lt;td>用快速模式或完整模式跑一次決策自檢&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/detailed-techniques/" data-link-title="WRAP 詳細技巧說明" data-link-desc="WRAP reference：每個階段的詳細操作技巧、範例與決策品質檢查。">詳細技巧&lt;/a>&lt;/td>
 &lt;td>補齊每階段的操作技巧與反偏誤方法&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="場景-2已熟悉原則想直接解決當前任務">場景 2：已熟悉原則、想直接解決當前任務&lt;/h3>
&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>要快速跑一輪 WRAP&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/pm-checklist/" data-link-title="決策者快速參考清單（PM Checklist）" data-link-desc="WRAP reference：快速模式、完整模式與決策品質自測清單。">pm-checklist&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>需要每階段詳細技巧&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/detailed-techniques/" data-link-title="WRAP 詳細技巧說明" data-link-desc="WRAP reference：每個階段的詳細操作技巧、範例與決策品質檢查。">detailed-techniques&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要設計絆腳索、失敗門檻或重新評估時機&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/tripwire-catalog/" data-link-title="絆腳索類型目錄" data-link-desc="WRAP reference：期限型、失敗型、偏離型、回退型、嘗試型、資料充足度與正面絆腳索的監控目錄。">tripwire-catalog&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要做深度查詢、反向驗證或多輪研究&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/iterative-research/" data-link-title="多輪迭代查詢方法論" data-link-desc="WRAP reference：用發散、具體化、精準化、反向驗證四輪結構支撐深度查詢與反向驗證。">iterative-research&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要檢查規則設計是否產生家長主義（paternalism）悖論&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/anti-paternalism/" data-link-title="悖論識別與自我暴露偏好" data-link-desc="WRAP reference：規則設計中的家長主義（paternalism）悖論檢查、自我參照風險、偏好透明化與反操控防護。">anti-paternalism&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>任務啟動前只想保留最低品質閘門&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/claim-quick-wrap/" data-link-title="任務啟動快速 WRAP（Claim Quick WRAP）" data-link-desc="WRAP reference：任務啟動前用 1 到 2 分鐘檢查選項、機會成本與失敗防護的快速協議。">claim-quick-wrap&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要把 WRAP 接進任務系統、規則庫或自動化提醒&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns/" data-link-title="WRAP Integration Patterns" data-link-desc="WRAP reference：把 WRAP 整合進任務系統、CLI、掛鉤（Hook）、規則庫與案例庫的可攜模式總覽。">integration-patterns&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要對齊觸發條件清單&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-triggers-alignment/" data-link-title="Triggers Alignment Pattern" data-link-desc="WRAP integration reference：讓文字規則、機器設定與自動提醒共享同一組觸發語意。">triggers-alignment&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要把 W/A/P 簡化成任務啟動三問&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-simplified-three-questions/" data-link-title="Simplified Three Questions" data-link-desc="WRAP integration reference：任務啟動前保留 W/A/P 最低品質門檻的簡化三問。">simplified-three-questions&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要防止偽擴增選項（pseudo-Widen）與假選項&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-pseudo-widen-guard/" data-link-title="偽擴增選項防護（Pseudo Widen Guard）" data-link-desc="WRAP integration reference：辨識假選項與根因假設單一化的偽擴增選項（pseudo-Widen）防護。">pseudo-widen-guard&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要做來源逐項核對&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-source-verification/" data-link-title="Source Verification Pattern" data-link-desc="WRAP integration reference：清單類答案逐項對照來源、防止候選幻覺混入結論。">source-verification&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要處理個人化建議的資料充足度&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-personalized-advice/" data-link-title="Personalized Advice Integration" data-link-desc="WRAP integration reference：把 Step 0 資料充足度閘門落到個人化建議對話。">personalized-advice&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要整理 WRAP 與專案規則庫分工&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-rules-map/" data-link-title="Rules Map Pattern" data-link-desc="WRAP integration reference：說明 WRAP 與專案規則庫如何分工與互補。">rules-map&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要把案例抽成可重用決策教訓&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-case-studies/" data-link-title="WRAP Case Study Pattern" data-link-desc="WRAP integration reference：把實戰事件轉成可重用決策教訓的案例庫模式。">case-studies&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每份 reference 自包含：讀任一份不需要回頭讀其他 reference。&lt;/p>
&lt;h2 id="與-blog-專案其他資料的關係">與 blog 專案其他資料的關係&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;code>.claude/skills/wrap-decision/&lt;/code>&lt;/td>
 &lt;td>實際 skill — Claude runtime 呼叫的檔案來源&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>content/skills/wrap-decision/&lt;/code>（本處）&lt;/td>
 &lt;td>文章版本 — 人類讀者在 blog 閱讀&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>content/skills/compositional-writing/&lt;/code>&lt;/td>
 &lt;td>寫作方法論 — 用於把 skill 內容整理成文章&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>content/skills/requirement-protocol/&lt;/code>&lt;/td>
 &lt;td>對話協議 — 與 WRAP 的決策呈現場景互補&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="last-updated">Last Updated&lt;/h2>
&lt;p>2026-05-04 — 同步到 &lt;code>.claude/skills/wrap-decision/&lt;/code> @ v2.3.0：&lt;/p></description><content:encoded><![CDATA[<h2 id="這個資料夾是什麼">這個資料夾是什麼</h2>
<p><code>wrap-decision</code> 是一套 WRAP 決策框架 skill，原生位置在 <a href="https://github.com/tarrragon/blog/tree/main/.claude/skills/wrap-decision"><code>.claude/skills/wrap-decision/</code></a> 供 Claude runtime 呼叫；這份是<strong>同內容的文章版本</strong>，讓人類讀者也能直接在 blog 閱讀。</p>
<p>核心是提醒決策者「你是有選擇的」。它把決策拆成錨點確認、Step 0 資料充足度閘門、W 擴增選項、R 現實檢驗、A 拉開距離、P 準備好犯錯，以及絆腳索（Tripwire）監控。</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/wrap-decision/skill/" data-link-title="WRAP 決策框架 — SKILL 入口" data-link-desc="WRAP 決策框架的 SKILL 入口：錨點確認、資料充足度閘門、擴增選項、現實檢驗、機會成本、行前預想與絆腳索。">SKILL 入口</a></td>
          <td>理解 WRAP 主流程、觸發條件與決策檢查順序</td>
      </tr>
      <tr>
          <td>2</td>
          <td><a href="/blog/skills/wrap-decision/pm-checklist/" data-link-title="決策者快速參考清單（PM Checklist）" data-link-desc="WRAP reference：快速模式、完整模式與決策品質自測清單。">決策者快速參考清單（PM Checklist）</a></td>
          <td>用快速模式或完整模式跑一次決策自檢</td>
      </tr>
      <tr>
          <td>3</td>
          <td><a href="/blog/skills/wrap-decision/detailed-techniques/" data-link-title="WRAP 詳細技巧說明" data-link-desc="WRAP reference：每個階段的詳細操作技巧、範例與決策品質檢查。">詳細技巧</a></td>
          <td>補齊每階段的操作技巧與反偏誤方法</td>
      </tr>
  </tbody>
</table>
<h3 id="場景-2已熟悉原則想直接解決當前任務">場景 2：已熟悉原則、想直接解決當前任務</h3>
<table>
  <thead>
      <tr>
          <th>觸發情境</th>
          <th>reference</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>要快速跑一輪 WRAP</td>
          <td><a href="/blog/skills/wrap-decision/pm-checklist/" data-link-title="決策者快速參考清單（PM Checklist）" data-link-desc="WRAP reference：快速模式、完整模式與決策品質自測清單。">pm-checklist</a></td>
      </tr>
      <tr>
          <td>需要每階段詳細技巧</td>
          <td><a href="/blog/skills/wrap-decision/detailed-techniques/" data-link-title="WRAP 詳細技巧說明" data-link-desc="WRAP reference：每個階段的詳細操作技巧、範例與決策品質檢查。">detailed-techniques</a></td>
      </tr>
      <tr>
          <td>要設計絆腳索、失敗門檻或重新評估時機</td>
          <td><a href="/blog/skills/wrap-decision/tripwire-catalog/" data-link-title="絆腳索類型目錄" data-link-desc="WRAP reference：期限型、失敗型、偏離型、回退型、嘗試型、資料充足度與正面絆腳索的監控目錄。">tripwire-catalog</a></td>
      </tr>
      <tr>
          <td>要做深度查詢、反向驗證或多輪研究</td>
          <td><a href="/blog/skills/wrap-decision/iterative-research/" data-link-title="多輪迭代查詢方法論" data-link-desc="WRAP reference：用發散、具體化、精準化、反向驗證四輪結構支撐深度查詢與反向驗證。">iterative-research</a></td>
      </tr>
      <tr>
          <td>要檢查規則設計是否產生家長主義（paternalism）悖論</td>
          <td><a href="/blog/skills/wrap-decision/anti-paternalism/" data-link-title="悖論識別與自我暴露偏好" data-link-desc="WRAP reference：規則設計中的家長主義（paternalism）悖論檢查、自我參照風險、偏好透明化與反操控防護。">anti-paternalism</a></td>
      </tr>
      <tr>
          <td>任務啟動前只想保留最低品質閘門</td>
          <td><a href="/blog/skills/wrap-decision/claim-quick-wrap/" data-link-title="任務啟動快速 WRAP（Claim Quick WRAP）" data-link-desc="WRAP reference：任務啟動前用 1 到 2 分鐘檢查選項、機會成本與失敗防護的快速協議。">claim-quick-wrap</a></td>
      </tr>
      <tr>
          <td>要把 WRAP 接進任務系統、規則庫或自動化提醒</td>
          <td><a href="/blog/skills/wrap-decision/integration-patterns/" data-link-title="WRAP Integration Patterns" data-link-desc="WRAP reference：把 WRAP 整合進任務系統、CLI、掛鉤（Hook）、規則庫與案例庫的可攜模式總覽。">integration-patterns</a></td>
      </tr>
      <tr>
          <td>要對齊觸發條件清單</td>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-triggers-alignment/" data-link-title="Triggers Alignment Pattern" data-link-desc="WRAP integration reference：讓文字規則、機器設定與自動提醒共享同一組觸發語意。">triggers-alignment</a></td>
      </tr>
      <tr>
          <td>要把 W/A/P 簡化成任務啟動三問</td>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-simplified-three-questions/" data-link-title="Simplified Three Questions" data-link-desc="WRAP integration reference：任務啟動前保留 W/A/P 最低品質門檻的簡化三問。">simplified-three-questions</a></td>
      </tr>
      <tr>
          <td>要防止偽擴增選項（pseudo-Widen）與假選項</td>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-pseudo-widen-guard/" data-link-title="偽擴增選項防護（Pseudo Widen Guard）" data-link-desc="WRAP integration reference：辨識假選項與根因假設單一化的偽擴增選項（pseudo-Widen）防護。">pseudo-widen-guard</a></td>
      </tr>
      <tr>
          <td>要做來源逐項核對</td>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-source-verification/" data-link-title="Source Verification Pattern" data-link-desc="WRAP integration reference：清單類答案逐項對照來源、防止候選幻覺混入結論。">source-verification</a></td>
      </tr>
      <tr>
          <td>要處理個人化建議的資料充足度</td>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-personalized-advice/" data-link-title="Personalized Advice Integration" data-link-desc="WRAP integration reference：把 Step 0 資料充足度閘門落到個人化建議對話。">personalized-advice</a></td>
      </tr>
      <tr>
          <td>要整理 WRAP 與專案規則庫分工</td>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-rules-map/" data-link-title="Rules Map Pattern" data-link-desc="WRAP integration reference：說明 WRAP 與專案規則庫如何分工與互補。">rules-map</a></td>
      </tr>
      <tr>
          <td>要把案例抽成可重用決策教訓</td>
          <td><a href="/blog/skills/wrap-decision/integration-patterns-case-studies/" data-link-title="WRAP Case Study Pattern" data-link-desc="WRAP integration reference：把實戰事件轉成可重用決策教訓的案例庫模式。">case-studies</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/wrap-decision/</code></td>
          <td>實際 skill — Claude runtime 呼叫的檔案來源</td>
      </tr>
      <tr>
          <td><code>content/skills/wrap-decision/</code>（本處）</td>
          <td>文章版本 — 人類讀者在 blog 閱讀</td>
      </tr>
      <tr>
          <td><code>content/skills/compositional-writing/</code></td>
          <td>寫作方法論 — 用於把 skill 內容整理成文章</td>
      </tr>
      <tr>
          <td><code>content/skills/requirement-protocol/</code></td>
          <td>對話協議 — 與 WRAP 的決策呈現場景互補</td>
      </tr>
  </tbody>
</table>
<h2 id="last-updated">Last Updated</h2>
<p>2026-05-04 — 同步到 <code>.claude/skills/wrap-decision/</code> @ v2.3.0：</p>
<ul>
<li>v2.1.0 — 新增多輪迭代查詢方法論、反向驗證範本、悖論識別檢查清單與自我暴露偏好實踐。</li>
<li>v2.2.0 — 新增反思深度質疑觸發條件。</li>
<li>v2.3.0 — 新增 CLI 自動駕駛（autopilot）、既有結論錨定（Anchor）、草率改規則與多步驟成功率盲點等決策路徑層干擾。</li>
</ul>
]]></content:encoded></item><item><title>WRAP 詳細技巧說明</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/detailed-techniques/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/detailed-techniques/</guid><description>&lt;p>本文件提供 WRAP 每個階段的詳細操作技巧、範例和判斷規則。&lt;/p>
&lt;hr>
&lt;h2 id="0-錨點確認--詳細操作">0. 錨點確認 — 詳細操作&lt;/h2>
&lt;h3 id="任務認領時的錨點確認">任務認領時的錨點確認&lt;/h3>
&lt;p>每個任務認領後、分析/派發前，確認核心目的：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>問題對&lt;/th>
 &lt;th>選項 A&lt;/th>
 &lt;th>選項 B&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>找 Bug（根因 + 最小修復）&lt;/td>
 &lt;td>系統設計改善（系統性重構）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>優先維度&lt;/td>
 &lt;td>使用者 UX 體驗&lt;/td>
 &lt;td>執行效能&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這是權重排序問題：在這個任務的脈絡下，哪個權重更高？&lt;/p>
&lt;h3 id="開發途中的重新確認">開發途中的重新確認&lt;/h3>
&lt;p>觸發重新確認的時機：&lt;/p>
&lt;ul>
&lt;li>資源耗盡時 → 「簡化 scope 快速交付，還是維持完整度？」&lt;/li>
&lt;li>發現額外問題時 → 「一起處理還是建任務延後？」&lt;/li>
&lt;li>方案需大幅修改時 → 「核心目的有沒有改變？」&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="w--擴增選項詳細技巧">W — 擴增選項詳細技巧&lt;/h2>
&lt;h3 id="爬梯子法範例">爬梯子法範例&lt;/h3>
&lt;p>以「掛鉤（Hook）錯誤處理」類的除錯場景為例：&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>0. 身邊&lt;/td>
 &lt;td>當前專案既有模組（例如追蹤器/派發器）&lt;/td>
 &lt;td>已有 API 但沒接線&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1. 同層&lt;/td>
 &lt;td>同類元件的清理機制&lt;/td>
 &lt;td>類似功能的掛鉤（Hook）已有方案&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2. 同領域&lt;/td>
 &lt;td>官方 GitHub Issues&lt;/td>
 &lt;td>多個 Issues 報告同樣症狀&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3. 跨領域&lt;/td>
 &lt;td>其他 extension host 錯誤處理&lt;/td>
 &lt;td>類比：宿主不應被擴充崩潰&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>教訓&lt;/strong>：在「身邊」層打轉多輪，不如首輪就爬到「同領域」層 — 往往 10 分鐘解決。&lt;/p>
&lt;h3 id="向外求解find-someone-whos-solved-your-problem">向外求解（Find Someone Who&amp;rsquo;s Solved Your Problem）&lt;/h3>
&lt;p>核心問句：「還有誰跟我一樣在為這個問題傷腦筋？我可以從他們那裡學到什麼？」&lt;/p>
&lt;p>做法：&lt;/p>
&lt;ol>
&lt;li>GitHub Issues 搜尋：&lt;code>{工具名} {症狀關鍵字}&lt;/code>&lt;/li>
&lt;li>Stack Overflow / 官方文件搜尋&lt;/li>
&lt;li>如果找到 → 可能根本不需要自己實作&lt;/li>
&lt;/ol>
&lt;h3 id="ai-內建知識調用">AI 內建知識調用&lt;/h3>
&lt;p>AI 擁有兩種「爬梯子」工具：&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>「CLI 掛鉤（Hook）系統的常見錯誤處理模式？」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>網路搜尋（WebSearch）&lt;/td>
 &lt;td>最新進展、特定版本&lt;/td>
 &lt;td>「Claude Code hooks 2026 hook error」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>擴增選項三步走：&lt;/p>
&lt;ol>
&lt;li>自問內建知識 → 產出 3-5 個已知方案&lt;/li>
&lt;li>網路搜尋（WebSearch）驗證 → 確認是否過時、發現新解法&lt;/li>
&lt;li>當前專案適配 → 選擇適合當前架構的方案&lt;/li>
&lt;/ol>
&lt;h3 id="假選項偵測規則季辛吉陷阱">假選項偵測規則（季辛吉陷阱）&lt;/h3>
&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>根因多元性&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>沒有「不做」或「問別人」的選項&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>框架測試&lt;/td>
 &lt;td>能一句話概括所有選項？&lt;/td>
 &lt;td>可以 → 框架太窄&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>範例案例：改 exit code / 改 stderr / 改 stdout → 本質都是「修改掛鉤（Hook）」→ 假選項。&lt;/p>
&lt;hr>
&lt;h2 id="r--現實檢驗詳細技巧">R — 現實檢驗詳細技巧&lt;/h2>
&lt;h3 id="問對問題基本率問法">問對問題：基本率問法&lt;/h3>
&lt;p>向 AI 問基本率（過去和現在），不問預測（未來）：&lt;/p>
&lt;ul>
&lt;li>「類似案子的重要變數是什麼？」&lt;/li>
&lt;li>「多少比率在 X 階段就解決？」&lt;/li>
&lt;li>「成功的案例有什麼共同特徵？」&lt;/li>
&lt;/ul>
&lt;p>AI 本質上是基本率的巨大資料庫 — 擅長回答「通常怎麼解決」而非「這次會不會成功」。&lt;/p>
&lt;h3 id="大範圍觀照zoom-out-近距離檢視zoom-in操作">大範圍觀照（Zoom Out）+ 近距離檢視（Zoom In）操作&lt;/h3>
&lt;p>&lt;strong>大範圍觀照（Zoom Out）&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>搜尋社群基本率：「多少人遇到同樣問題？」&lt;/li>
&lt;li>AI 內建知識：「這類問題通常怎麼解決？」&lt;/li>
&lt;li>統計性判斷：「做了這決定，合理預期會發生什麼？」&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>近距離檢視（Zoom In）&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>讀具體案例內文（不只 Issue 標題）&lt;/li>
&lt;li>讀 source code（不只代理人報告，實際 git diff）&lt;/li>
&lt;li>小規模實驗驗證假設&lt;/li>
&lt;/ul>
&lt;p>如同羅斯福的訊息策略：不完全依賴單一來源，多源交叉驗證。&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>hook error&lt;/td>
 &lt;td>直接改核心函式&lt;/td>
 &lt;td>先手動測試一個 hook 的 exit code&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>語意重命名&lt;/td>
 &lt;td>一次派發改 5 個檔案&lt;/td>
 &lt;td>先改 1 個檔案確認消費端不壞&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>新功能設計&lt;/td>
 &lt;td>完整實作後才測試&lt;/td>
 &lt;td>先寫最小概念驗證（POC）驗證核心假設&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>試水溫成本：5 分鐘。全力投入失敗成本：2 次修改 + 1 次回退 + 問題惡化。&lt;/p></description><content:encoded><![CDATA[<p>本文件提供 WRAP 每個階段的詳細操作技巧、範例和判斷規則。</p>
<hr>
<h2 id="0-錨點確認--詳細操作">0. 錨點確認 — 詳細操作</h2>
<h3 id="任務認領時的錨點確認">任務認領時的錨點確認</h3>
<p>每個任務認領後、分析/派發前，確認核心目的：</p>
<table>
  <thead>
      <tr>
          <th>問題對</th>
          <th>選項 A</th>
          <th>選項 B</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>交付策略</td>
          <td>快速交付（最小修改）</td>
          <td>穩定架構（花時間做對）</td>
      </tr>
      <tr>
          <td>問題性質</td>
          <td>找 Bug（根因 + 最小修復）</td>
          <td>系統設計改善（系統性重構）</td>
      </tr>
      <tr>
          <td>優先維度</td>
          <td>使用者 UX 體驗</td>
          <td>執行效能</td>
      </tr>
  </tbody>
</table>
<p>這是權重排序問題：在這個任務的脈絡下，哪個權重更高？</p>
<h3 id="開發途中的重新確認">開發途中的重新確認</h3>
<p>觸發重新確認的時機：</p>
<ul>
<li>資源耗盡時 → 「簡化 scope 快速交付，還是維持完整度？」</li>
<li>發現額外問題時 → 「一起處理還是建任務延後？」</li>
<li>方案需大幅修改時 → 「核心目的有沒有改變？」</li>
</ul>
<hr>
<h2 id="w--擴增選項詳細技巧">W — 擴增選項詳細技巧</h2>
<h3 id="爬梯子法範例">爬梯子法範例</h3>
<p>以「掛鉤（Hook）錯誤處理」類的除錯場景為例：</p>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>搜尋</th>
          <th>結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0. 身邊</td>
          <td>當前專案既有模組（例如追蹤器/派發器）</td>
          <td>已有 API 但沒接線</td>
      </tr>
      <tr>
          <td>1. 同層</td>
          <td>同類元件的清理機制</td>
          <td>類似功能的掛鉤（Hook）已有方案</td>
      </tr>
      <tr>
          <td>2. 同領域</td>
          <td>官方 GitHub Issues</td>
          <td>多個 Issues 報告同樣症狀</td>
      </tr>
      <tr>
          <td>3. 跨領域</td>
          <td>其他 extension host 錯誤處理</td>
          <td>類比：宿主不應被擴充崩潰</td>
      </tr>
  </tbody>
</table>
<p><strong>教訓</strong>：在「身邊」層打轉多輪，不如首輪就爬到「同領域」層 — 往往 10 分鐘解決。</p>
<h3 id="向外求解find-someone-whos-solved-your-problem">向外求解（Find Someone Who&rsquo;s Solved Your Problem）</h3>
<p>核心問句：「還有誰跟我一樣在為這個問題傷腦筋？我可以從他們那裡學到什麼？」</p>
<p>做法：</p>
<ol>
<li>GitHub Issues 搜尋：<code>{工具名} {症狀關鍵字}</code></li>
<li>Stack Overflow / 官方文件搜尋</li>
<li>如果找到 → 可能根本不需要自己實作</li>
</ol>
<h3 id="ai-內建知識調用">AI 內建知識調用</h3>
<p>AI 擁有兩種「爬梯子」工具：</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>適用場景</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>內建知識</td>
          <td>經典解法、設計模式</td>
          <td>「CLI 掛鉤（Hook）系統的常見錯誤處理模式？」</td>
      </tr>
      <tr>
          <td>網路搜尋（WebSearch）</td>
          <td>最新進展、特定版本</td>
          <td>「Claude Code hooks 2026 hook error」</td>
      </tr>
  </tbody>
</table>
<p>擴增選項三步走：</p>
<ol>
<li>自問內建知識 → 產出 3-5 個已知方案</li>
<li>網路搜尋（WebSearch）驗證 → 確認是否過時、發現新解法</li>
<li>當前專案適配 → 選擇適合當前架構的方案</li>
</ol>
<h3 id="假選項偵測規則季辛吉陷阱">假選項偵測規則（季辛吉陷阱）</h3>
<p>季辛吉回憶錄：國務院給尼克森三選項（核戰/現狀/投降），實際只有「現狀」可選。</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>沒有「不做」或「問別人」的選項</td>
      </tr>
      <tr>
          <td>框架測試</td>
          <td>能一句話概括所有選項？</td>
          <td>可以 → 框架太窄</td>
      </tr>
  </tbody>
</table>
<p>範例案例：改 exit code / 改 stderr / 改 stdout → 本質都是「修改掛鉤（Hook）」→ 假選項。</p>
<hr>
<h2 id="r--現實檢驗詳細技巧">R — 現實檢驗詳細技巧</h2>
<h3 id="問對問題基本率問法">問對問題：基本率問法</h3>
<p>向 AI 問基本率（過去和現在），不問預測（未來）：</p>
<ul>
<li>「類似案子的重要變數是什麼？」</li>
<li>「多少比率在 X 階段就解決？」</li>
<li>「成功的案例有什麼共同特徵？」</li>
</ul>
<p>AI 本質上是基本率的巨大資料庫 — 擅長回答「通常怎麼解決」而非「這次會不會成功」。</p>
<h3 id="大範圍觀照zoom-out-近距離檢視zoom-in操作">大範圍觀照（Zoom Out）+ 近距離檢視（Zoom In）操作</h3>
<p><strong>大範圍觀照（Zoom Out）</strong>：</p>
<ul>
<li>搜尋社群基本率：「多少人遇到同樣問題？」</li>
<li>AI 內建知識：「這類問題通常怎麼解決？」</li>
<li>統計性判斷：「做了這決定，合理預期會發生什麼？」</li>
</ul>
<p><strong>近距離檢視（Zoom In）</strong>：</p>
<ul>
<li>讀具體案例內文（不只 Issue 標題）</li>
<li>讀 source code（不只代理人報告，實際 git diff）</li>
<li>小規模實驗驗證假設</li>
</ul>
<p>如同羅斯福的訊息策略：不完全依賴單一來源，多源交叉驗證。</p>
<h3 id="試水溫操作範例">試水溫操作範例</h3>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>全力投入（錯誤）</th>
          <th>試水溫（正確）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>hook error</td>
          <td>直接改核心函式</td>
          <td>先手動測試一個 hook 的 exit code</td>
      </tr>
      <tr>
          <td>語意重命名</td>
          <td>一次派發改 5 個檔案</td>
          <td>先改 1 個檔案確認消費端不壞</td>
      </tr>
      <tr>
          <td>新功能設計</td>
          <td>完整實作後才測試</td>
          <td>先寫最小概念驗證（POC）驗證核心假設</td>
      </tr>
  </tbody>
</table>
<p>試水溫成本：5 分鐘。全力投入失敗成本：2 次修改 + 1 次回退 + 問題惡化。</p>
<h3 id="最強版本論證流程">最強版本論證流程</h3>
<p>來源：努金調解索尼 vs 蘋果 — 「我會陳述對方的立場，且優於其自我陳述。」</p>
<p>操作步驟：</p>
<ol>
<li>列出被放棄選項的優點（比其支持者說的更完整）</li>
<li>列出選定方案的缺點（至少 3 個）</li>
<li>自問：「我能否闡明反對意見，且優於反對者自己的陳述？」
<ul>
<li>能 → 真的理解了取捨（trade-off）</li>
<li>說明不足 → 可能還在確認偏誤中 → 回到 R 階段</li>
</ul>
</li>
</ol>
<hr>
<h2 id="a--拉開距離詳細技巧">A — 拉開距離詳細技巧</h2>
<h3 id="機會成本顯性化範例">機會成本顯性化範例</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">選項 A：修改核心錯誤處理函式
</span></span><span class="line"><span class="ln">2</span><span class="cl">  機會成本：花 2 小時可能白做，這段時間可以推進其他任務
</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">  不選其他的代價：沒有搜尋社群，可能 10 分鐘就有答案
</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：搜尋 GitHub Issues
</span></span><span class="line"><span class="ln">7</span><span class="cl">  機會成本：10 分鐘
</span></span><span class="line"><span class="ln">8</span><span class="cl">  風險：找不到相關 Issue
</span></span><span class="line"><span class="ln">9</span><span class="cl">  不選其他的代價：如果是自己的 bug 就需要回來修</span></span></code></pre></div><h3 id="工具選擇檢查tool-selection-check詳細技巧">工具選擇檢查（Tool selection check）詳細技巧</h3>
<p><strong>問題背景</strong>：LLM 在選工具（tool）時常見一種傾向：單步決策敏感、總步驟數盲點。容易把「3 步繞路」誤估為「1 步直達」，因為每個單步看起來都合理。物化工具（tool）名稱（「用 Write」）會進一步窄化思考框架。</p>
<p><strong>觸發條件</strong>：</p>
<ul>
<li>選工具（tool）前（選擇具體工具呼叫的那個瞬間）</li>
<li>預估步驟數 &gt; 2（有 2 個以上 tool call 才完成目的）</li>
<li>涉及 Write/Bash 組合（尤其 Write 到中介檔再讀回再寫入的模式）</li>
</ul>
<p><strong>4 問操作流程</strong>：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>自我質疑</th>
          <th>回答「是」的行動</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Q1 物化檢查</td>
          <td>我描述任務時用的是工具（tool）名稱（「用 Write 建 ctx 檔」），還是目的語言（「把 context 寫進任務紀錄欄位」）?</td>
          <td>重寫任務描述為目的語言，再重選工具（tool）</td>
      </tr>
      <tr>
          <td>Q2 步驟數檢查</td>
          <td>列出實際 tool call 序列，數一下是幾步? 有無「1 步路徑」（單一 tool call 直接完成）被忽略?</td>
          <td>換成 1 步路徑（heredoc / Edit 直接改 md）</td>
      </tr>
      <tr>
          <td>Q3 目的地檢查</td>
          <td>產出的最終目的地是哪裡?（CLI stdout / 檔案系統 / 任務紀錄 / log）工具選擇是否匹配目的地?</td>
          <td>目的地若是任務紀錄 → 首選 Edit；若是 append CLI → 首選 heredoc Bash</td>
      </tr>
      <tr>
          <td>Q4 白名單檢查</td>
          <td>我選的 tool 是否在「低摩擦首選」名單外? 有無不必要引入中介檔?</td>
          <td>換成白名單內工具</td>
      </tr>
  </tbody>
</table>
<p><strong>「低摩擦首選」白名單</strong>（長文寫入類）：</p>
<table>
  <thead>
      <tr>
          <th>目的地</th>
          <th>首選工具（tool）</th>
          <th>次選</th>
          <th>避免</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>寫入任務紀錄欄位</td>
          <td>Edit（直接編輯紀錄）</td>
          <td><code>task append</code> + heredoc</td>
          <td>Write 暫存檔 → Read → Bash</td>
      </tr>
      <tr>
          <td>執行 CLI 含長參數</td>
          <td>heredoc <code>$(cat &lt;&lt;'EOF'...EOF)</code></td>
          <td>單引號整段</td>
          <td>Write → Bash 讀入</td>
      </tr>
      <tr>
          <td>寫 commit message</td>
          <td>heredoc</td>
          <td>多行 <code>-m</code></td>
          <td>Write 中介檔</td>
      </tr>
  </tbody>
</table>
<h3 id="情境反向驗證">情境反向驗證</h3>
<p><strong>原場景（繞路）</strong>：決策者要把 1.5KB context 寫入任務紀錄的 Solution 欄位，選擇：</p>
<ol>
<li><code>Write /tmp/ctx.md</code> 建暫存檔</li>
<li><code>Read /tmp/ctx.md</code> 讀回</li>
<li><code>Bash: task append --file /tmp/ctx.md</code></li>
</ol>
<p><strong>4 問驗證</strong>：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>原場景答案</th>
          <th>結論</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Q1 物化</td>
          <td>「用 Write 建 ctx 檔」(物化) vs「把 context 寫進任務紀錄」(目的)</td>
          <td>觸發 → 重寫為目的語言</td>
      </tr>
      <tr>
          <td>Q2 步驟數</td>
          <td>3 步（Write / Read / Bash） vs 1 步（heredoc Bash 或 Edit 任務紀錄）</td>
          <td>觸發 → 有 1 步路徑被忽略</td>
      </tr>
      <tr>
          <td>Q3 目的地</td>
          <td>目的地是任務紀錄的 Solution 欄位；/tmp 中介檔非必要</td>
          <td>觸發 → 應首選 Edit</td>
      </tr>
      <tr>
          <td>Q4 白名單</td>
          <td>Write 到 /tmp 屬白名單外繞路</td>
          <td>觸發 → 換 heredoc 或 Edit</td>
      </tr>
  </tbody>
</table>
<p><strong>正確路徑（1 步直達）</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">task append TASK-ID --section <span class="s2">&#34;Solution&#34;</span> <span class="s2">&#34;</span><span class="k">$(</span>cat <span class="s">&lt;&lt;&#39;EOF&#39;
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="s">（content here）
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">)</span><span class="s2">&#34;</span></span></span></code></pre></div><p>或更直接：<code>Edit</code> 工具打開任務紀錄編輯 Solution 欄位（0 步 tool call，整合在 Edit 單次呼叫內）。</p>
<p><strong>教訓</strong>：4 問任一觸發，都是「繞路訊號」——單步都合理不代表總路徑短。</p>
<h3 id="核心優先事項排序">核心優先事項排序</h3>
<p>典型軟體專案的優先事項（由高到低）僅供參考，各專案應建立自己的排序：</p>
<ol>
<li>產品功能開發</li>
<li>程式碼品質</li>
<li>開發流程改善</li>
<li>工具維護</li>
</ol>
<p>衝突時用排序終結爭論：第 4 項 vs 第 1 項 → 第 1 項勝 → 工具維護建任務延後。</p>
<hr>
<h2 id="p--準備好犯錯詳細技巧">P — 準備好犯錯詳細技巧</h2>
<h3 id="行前預想操作">行前預想操作</h3>
<p>Gary Klein 方法：先假設計畫失敗了，然後問「是什麼殺了它？」</p>
<ul>
<li>比問「可能會失敗嗎？」多產出 25% 的洞察（且更具體）</li>
</ul>
<p>除錯情境的行前預想範例（如果當時做了）：</p>
<ul>
<li>失敗原因 1：根因判斷錯誤（可能性 &gt; 50%）→ 已超過閾值，不應執行</li>
<li>失敗原因 2：影響範圍不清（可能性 40%）</li>
<li>失敗原因 3：實際執行環境行為不如預期（可能性 60%）</li>
</ul>
<h3 id="安全係數參考">安全係數參考</h3>
<table>
  <thead>
      <tr>
          <th>領域</th>
          <th>安全倍數</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>電梯鋼纜</td>
          <td>x11</td>
      </tr>
      <tr>
          <td>太空梭地面設施</td>
          <td>x4</td>
      </tr>
      <tr>
          <td>微軟軟體專案</td>
          <td>+30% 時間</td>
      </tr>
      <tr>
          <td>代理人任務派發</td>
          <td>預估時間 x 1.3-1.5（經驗起點、依任務調整；非實證數字）</td>
      </tr>
  </tbody>
</table>
<p>展現謙卑：我們容易過度自信，所以預留緩衝。前三列為外部來源，最後一列是本框架的經驗起點、與 SKILL.md P 階段一致。</p>
<hr>
<p><strong>Last Updated</strong>: 2026-04-18
<strong>Version</strong>: 1.2.0 — A 階段新增工具選擇檢查（Tool selection check）4 問 + 情境反向驗證</p>
]]></content:encoded></item><item><title>任務啟動快速 WRAP（Claim Quick WRAP）</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/claim-quick-wrap/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/claim-quick-wrap/</guid><description>&lt;p>任務啟動快速 WRAP（Claim Quick WRAP）的責任是在開始一個任務前，用 1-2 分鐘檢查選項、機會成本與失敗防護。它提供任務啟動前的最低品質閘門；高風險任務接著進入完整 WRAP。&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>接手小型實作任務&lt;/td>
 &lt;td>跑 W/A/P 三問即可開始&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>接手文件或規則任務&lt;/td>
 &lt;td>跑 W/A/P，並補一個來源或既有規則檢查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>接手分析或根因任務&lt;/td>
 &lt;td>快速 WRAP（Quick WRAP）只做啟動檢查，接著跑完整 WRAP&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>三問中出現高風險答案&lt;/td>
 &lt;td>暫停任務，改跑完整 WRAP&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="三問模板">三問模板&lt;/h2>
&lt;h3 id="w還有什麼路徑">W：還有什麼路徑&lt;/h3>
&lt;p>&lt;strong>問題&lt;/strong>：除了我第一個想到的做法，還有哪兩個可行路徑？&lt;/p>
&lt;p>&lt;strong>最低回答格式&lt;/strong>：&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">- 首選路徑：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">- 替代路徑 A：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">- 替代路徑 B：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">- 放棄理由：...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>通過標準&lt;/strong>：至少有兩個替代路徑，且差異落在假設或交付方式上，避免只做同一路徑的參數微調。&lt;/p>
&lt;h3 id="a會擠壓什麼">A：會擠壓什麼&lt;/h3>
&lt;p>&lt;strong>問題&lt;/strong>：執行這個任務會擠壓哪個更重要的目標或工作？&lt;/p>
&lt;p>&lt;strong>最低回答格式&lt;/strong>：&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">- 預估投入：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">- 擠壓目標：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">- 延後代價：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">- 現在做的理由：...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>通過標準&lt;/strong>：回答要指向具體目標、里程碑或使用者價值，避免只寫「影響進度」。&lt;/p>
&lt;h3 id="p最可能怎麼失敗">P：最可能怎麼失敗&lt;/h3>
&lt;p>&lt;strong>問題&lt;/strong>：如果這個任務完成後仍然失敗，最可能的三個原因是什麼？&lt;/p>
&lt;p>&lt;strong>最低回答格式&lt;/strong>：&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">- 失敗原因 1：...；防護：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">- 失敗原因 2：...；防護：...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">- 失敗原因 3：...；防護：...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>通過標準&lt;/strong>：每個失敗原因都有對應防護；任一原因機率高於 50% 時，先調整方案再開始。&lt;/p>
&lt;hr>
&lt;h2 id="任務類型差異">任務類型差異&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>任務類型&lt;/th>
 &lt;th>W 重點&lt;/th>
 &lt;th>A 重點&lt;/th>
 &lt;th>P 重點&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>實作任務&lt;/td>
 &lt;td>比較新增、改造既有、純文件或零工具方案&lt;/td>
 &lt;td>檢查是否擠壓核心功能或修復&lt;/td>
 &lt;td>防止測試不足、範圍外擴張、回退成本過高&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>文件任務&lt;/td>
 &lt;td>比較新增文章、補既有文章、補索引或補卡片&lt;/td>
 &lt;td>檢查是否擠壓更高價值章節&lt;/td>
 &lt;td>防止重複、連結失效、抽象層錯置&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>分析任務&lt;/td>
 &lt;td>比較多個根因假設&lt;/td>
 &lt;td>檢查是否擠壓可直接驗證的工作&lt;/td>
 &lt;td>防止過早收斂、缺來源、缺反向驗證&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>規則任務&lt;/td>
 &lt;td>比較改規則、改流程、加提示或加檢查&lt;/td>
 &lt;td>檢查是否增加長期摩擦&lt;/td>
 &lt;td>防止規則自相矛盾或過度限制使用者&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="升級完整-wrap-的訊號">升級完整 WRAP 的訊號&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>W 階段選項品質不足&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>替代方案都指向同一假設&lt;/td>
 &lt;td>偽擴增選項（pseudo-Widen），需要重新定義問題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>機會成本大於任務價值&lt;/td>
 &lt;td>A 階段需要重新排序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>最可能失敗原因高於 50%&lt;/td>
 &lt;td>P 階段已顯示方案脆弱&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>任務屬於根因分析、提案評估、重大架構決策&lt;/td>
 &lt;td>快速 WRAP（Quick WRAP）資訊量不足&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="常見反模式">常見反模式&lt;/h2>
&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>指出被延後的具體目標&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>用快速 WRAP（Quick WRAP）取代完整 WRAP&lt;/td>
 &lt;td>進入完整 WRAP，補現實檢驗（Reality Test）與反向驗證&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="可複製提示">可複製提示&lt;/h2>





&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">開始前先跑快速 WRAP（Quick WRAP）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">W：除了第一個方案，還有哪兩條可行路徑？為什麼選現在這條？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">A：這會擠壓哪個更重要的目標？現在做的理由是什麼？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">P：如果完成後仍失敗，最可能的三個原因是什麼？各自的防護是什麼？&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>&lt;strong>Last Updated&lt;/strong>: 2026-04-30
&lt;strong>Version&lt;/strong>: 2.0.0 — 泛化為 portable 任務啟動檢查，移除特定任務系統、CLI 類別與內部編號依賴。&lt;/p></description><content:encoded><![CDATA[<p>任務啟動快速 WRAP（Claim Quick WRAP）的責任是在開始一個任務前，用 1-2 分鐘檢查選項、機會成本與失敗防護。它提供任務啟動前的最低品質閘門；高風險任務接著進入完整 WRAP。</p>
<hr>
<h2 id="使用時機">使用時機</h2>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>使用方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>接手小型實作任務</td>
          <td>跑 W/A/P 三問即可開始</td>
      </tr>
      <tr>
          <td>接手文件或規則任務</td>
          <td>跑 W/A/P，並補一個來源或既有規則檢查</td>
      </tr>
      <tr>
          <td>接手分析或根因任務</td>
          <td>快速 WRAP（Quick WRAP）只做啟動檢查，接著跑完整 WRAP</td>
      </tr>
      <tr>
          <td>三問中出現高風險答案</td>
          <td>暫停任務，改跑完整 WRAP</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="三問模板">三問模板</h2>
<h3 id="w還有什麼路徑">W：還有什麼路徑</h3>
<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">- 替代路徑 A：...
</span></span><span class="line"><span class="ln">3</span><span class="cl">- 替代路徑 B：...
</span></span><span class="line"><span class="ln">4</span><span class="cl">- 放棄理由：...</span></span></code></pre></div><p><strong>通過標準</strong>：至少有兩個替代路徑，且差異落在假設或交付方式上，避免只做同一路徑的參數微調。</p>
<h3 id="a會擠壓什麼">A：會擠壓什麼</h3>
<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></code></pre></div><p><strong>通過標準</strong>：回答要指向具體目標、里程碑或使用者價值，避免只寫「影響進度」。</p>
<h3 id="p最可能怎麼失敗">P：最可能怎麼失敗</h3>
<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">- 失敗原因 1：...；防護：...
</span></span><span class="line"><span class="ln">2</span><span class="cl">- 失敗原因 2：...；防護：...
</span></span><span class="line"><span class="ln">3</span><span class="cl">- 失敗原因 3：...；防護：...</span></span></code></pre></div><p><strong>通過標準</strong>：每個失敗原因都有對應防護；任一原因機率高於 50% 時，先調整方案再開始。</p>
<hr>
<h2 id="任務類型差異">任務類型差異</h2>
<table>
  <thead>
      <tr>
          <th>任務類型</th>
          <th>W 重點</th>
          <th>A 重點</th>
          <th>P 重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>實作任務</td>
          <td>比較新增、改造既有、純文件或零工具方案</td>
          <td>檢查是否擠壓核心功能或修復</td>
          <td>防止測試不足、範圍外擴張、回退成本過高</td>
      </tr>
      <tr>
          <td>文件任務</td>
          <td>比較新增文章、補既有文章、補索引或補卡片</td>
          <td>檢查是否擠壓更高價值章節</td>
          <td>防止重複、連結失效、抽象層錯置</td>
      </tr>
      <tr>
          <td>分析任務</td>
          <td>比較多個根因假設</td>
          <td>檢查是否擠壓可直接驗證的工作</td>
          <td>防止過早收斂、缺來源、缺反向驗證</td>
      </tr>
      <tr>
          <td>規則任務</td>
          <td>比較改規則、改流程、加提示或加檢查</td>
          <td>檢查是否增加長期摩擦</td>
          <td>防止規則自相矛盾或過度限制使用者</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="升級完整-wrap-的訊號">升級完整 WRAP 的訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>升級原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>只有一個選項</td>
          <td>W 階段選項品質不足</td>
      </tr>
      <tr>
          <td>替代方案都指向同一假設</td>
          <td>偽擴增選項（pseudo-Widen），需要重新定義問題</td>
      </tr>
      <tr>
          <td>機會成本大於任務價值</td>
          <td>A 階段需要重新排序</td>
      </tr>
      <tr>
          <td>最可能失敗原因高於 50%</td>
          <td>P 階段已顯示方案脆弱</td>
      </tr>
      <tr>
          <td>任務屬於根因分析、提案評估、重大架構決策</td>
          <td>快速 WRAP（Quick WRAP）資訊量不足</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="常見反模式">常見反模式</h2>
<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>指出被延後的具體目標</td>
      </tr>
      <tr>
          <td>防護不可驗證</td>
          <td>防護寫「小心」或「注意」</td>
          <td>改成測試、檢查、回退或限制範圍</td>
      </tr>
      <tr>
          <td>分析任務只跑三問</td>
          <td>用快速 WRAP（Quick WRAP）取代完整 WRAP</td>
          <td>進入完整 WRAP，補現實檢驗（Reality Test）與反向驗證</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="可複製提示">可複製提示</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">開始前先跑快速 WRAP（Quick WRAP）：
</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">W：除了第一個方案，還有哪兩條可行路徑？為什麼選現在這條？
</span></span><span class="line"><span class="ln">4</span><span class="cl">A：這會擠壓哪個更重要的目標？現在做的理由是什麼？
</span></span><span class="line"><span class="ln">5</span><span class="cl">P：如果完成後仍失敗，最可能的三個原因是什麼？各自的防護是什麼？</span></span></code></pre></div><hr>
<p><strong>Last Updated</strong>: 2026-04-30
<strong>Version</strong>: 2.0.0 — 泛化為 portable 任務啟動檢查，移除特定任務系統、CLI 類別與內部編號依賴。</p>
]]></content:encoded></item><item><title>多輪迭代查詢方法論</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/iterative-research/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/iterative-research/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>本檔位置&lt;/strong>：W 階段 + R 階段擴充。WRAP 既有的「擴增選項」與「現實檢驗」原則本身不規範&lt;strong>搜尋的迭代結構&lt;/strong>，本檔補充。&lt;/p>
&lt;p>&lt;strong>來源&lt;/strong>：一次規則設計過程中，4 輪查詢累積約 27 個洞察（n=1 回顧、非統計），第 4 輪反向驗證揭露核心悖論。&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>不做反向搜尋&lt;/td>
 &lt;td>落入確認偏誤&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>不做精準化深挖&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;hr>
&lt;h2 id="四輪結構">四輪結構&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>對應 WRAP&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1&lt;/td>
 &lt;td>發散型 (Divergent)&lt;/td>
 &lt;td>廣泛關鍵字鋪面，5-8 個不同領域&lt;/td>
 &lt;td>建立問題地圖，找出所有可能相關方向&lt;/td>
 &lt;td>W 擴增初步&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2&lt;/td>
 &lt;td>具體化 (Concrete)&lt;/td>
 &lt;td>基於第一輪發現深入特定主題&lt;/td>
 &lt;td>將抽象主題轉為具體案例&lt;/td>
 &lt;td>W 擴增深化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3&lt;/td>
 &lt;td>精準化 (Precise)&lt;/td>
 &lt;td>從前兩輪提煉精準關鍵字深挖&lt;/td>
 &lt;td>找到最高品質的學術來源與案例細節&lt;/td>
 &lt;td>R 現實檢驗&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4&lt;/td>
 &lt;td>反向驗證 (Inverse)&lt;/td>
 &lt;td>每個結論找反例 / 批評 / 反駁&lt;/td>
 &lt;td>防止確認偏誤，揭露悖論&lt;/td>
 &lt;td>R 現實檢驗 + A 拉開距離&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="進入下一輪訊號">進入下一輪訊號&lt;/h2>
&lt;p>至少 5 條判定訊號，任一觸發即可進入下一輪：&lt;/p>
&lt;ol>
&lt;li>該輪新洞察 &amp;lt; 上一輪 50%（經驗閾值）→ 邊際效益遞減（自然進入下輪或收斂）&lt;/li>
&lt;li>已涵蓋正反雙面 → 可進入收斂&lt;/li>
&lt;li>用戶補充重大反向觀察 → 立即進入反向驗證輪&lt;/li>
&lt;li>發現自我參照悖論 → 進入元層次反思輪&lt;/li>
&lt;li>出現結論互相衝突 → 進入優先級裁決輪&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="邊界條件">邊界條件&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>不一定要 4 輪&lt;/strong>：簡單議題可能 2 輪夠&lt;/li>
&lt;li>&lt;strong>反向驗證輪幾乎不可省略&lt;/strong>（確認偏誤防護）&lt;/li>
&lt;li>&lt;strong>每輪 8 個搜尋是 網路搜尋（WebSearch）平行查詢上限的經驗值&lt;/strong>——可調整&lt;/li>
&lt;li>&lt;strong>每輪後必暴露偏好&lt;/strong>（不偽裝中立），讓用戶可介入糾正方向&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="反向驗證實踐範本第-4-輪標準格式">反向驗證實踐範本（第 4 輪標準格式）&lt;/h2>
&lt;h3 id="標準表格格式">標準表格格式&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">|----------|-----------|
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">| [結論 1] | [批評 / 反例 / 反駁 / 限制] |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">| [結論 2] | [批評 / 反例 / 反駁 / 限制] |&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="8-種反向方向類型">8 種反向方向類型&lt;/h3>
&lt;p>每輪反向搜尋必涵蓋至少 4 種，理想涵蓋全部 8 種：&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>1. 方法論的學術批評&lt;/td>
 &lt;td>此方法論本身有什麼學術反駁？&lt;/td>
 &lt;td>MI 缺乏理論基礎、Voss 不適用長期關係&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2. 關鍵案例的反方論點&lt;/td>
 &lt;td>案例的另一方怎麼說？&lt;/td>
 &lt;td>冤案的警方/檢察官立場&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3. 結論的失效情境&lt;/td>
 &lt;td>在什麼情境此結論失效？&lt;/td>
 &lt;td>MI 對強制 client 失效&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4. 數字 / 統計的方法論限制&lt;/td>
 &lt;td>數字背後的方法論有什麼限制？&lt;/td>
 &lt;td>DarkBench 30-61% 的 sampling 限制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>5. 普適性的文化 / 情境限制&lt;/td>
 &lt;td>此結論在不同文化 / 情境是否成立？&lt;/td>
 &lt;td>Voss 不適用長期關係、自主性（autonomy）是西方偏見&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>6. 隱性家長主義（paternalism）警示&lt;/td>
 &lt;td>此規則是否本身違反它要保護的價值？&lt;/td>
 &lt;td>保護用戶自主性（autonomy）的規則限制了決策者自主性（decision-maker autonomy）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>7. 取捨揭露（trade-off）&lt;/td>
 &lt;td>此方案的隱藏代價是什麼？&lt;/td>
 &lt;td>迎合（sycophancy）提升滿意度但傷害福祉&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>8. 自我參照悖論識別&lt;/td>
 &lt;td>此規則的「正確示範」是否本身違反它要禁止的模式？&lt;/td>
 &lt;td>反窄框架（narrow framing）本身就是窄框架（narrow framing）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="單次規則設計的查詢紀錄n1-回顧">單次規則設計的查詢紀錄（n=1 回顧）&lt;/h2>
&lt;blockquote>
&lt;p>下表是&lt;strong>一次&lt;/strong>規則設計過程的事後回顧、不是可複製的統計規律。數字（搜尋數 / 洞察數 / 累積）來自該次的記憶整理、樣本量 n=1。用它理解「四輪結構大致怎麼推進」、不要當成「每次都會這樣」的基準率 —— 這正是本檔自己倡導的「基本率 &amp;gt; 預測、不把單次回憶當統計」。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p><strong>本檔位置</strong>：W 階段 + R 階段擴充。WRAP 既有的「擴增選項」與「現實檢驗」原則本身不規範<strong>搜尋的迭代結構</strong>，本檔補充。</p>
<p><strong>來源</strong>：一次規則設計過程中，4 輪查詢累積約 27 個洞察（n=1 回顧、非統計），第 4 輪反向驗證揭露核心悖論。</p></blockquote>
<hr>
<h2 id="為什麼一次性搜尋不夠">為什麼一次性搜尋不夠</h2>
<table>
  <thead>
      <tr>
          <th>失敗模式</th>
          <th>後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>第一輪關鍵字基於「目前理解」設定</td>
          <td>找到的多是「已知方向的支援證據」</td>
      </tr>
      <tr>
          <td>不做反向搜尋</td>
          <td>落入確認偏誤</td>
      </tr>
      <tr>
          <td>不做精準化深挖</td>
          <td>停留在表面案例，缺學術深度</td>
      </tr>
      <tr>
          <td>不做用戶介入點記錄</td>
          <td>用戶補充的盲點訊息流失</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="四輪結構">四輪結構</h2>
<table>
  <thead>
      <tr>
          <th>輪次</th>
          <th>名稱</th>
          <th>關鍵字策略</th>
          <th>目的</th>
          <th>對應 WRAP</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>發散型 (Divergent)</td>
          <td>廣泛關鍵字鋪面，5-8 個不同領域</td>
          <td>建立問題地圖，找出所有可能相關方向</td>
          <td>W 擴增初步</td>
      </tr>
      <tr>
          <td>2</td>
          <td>具體化 (Concrete)</td>
          <td>基於第一輪發現深入特定主題</td>
          <td>將抽象主題轉為具體案例</td>
          <td>W 擴增深化</td>
      </tr>
      <tr>
          <td>3</td>
          <td>精準化 (Precise)</td>
          <td>從前兩輪提煉精準關鍵字深挖</td>
          <td>找到最高品質的學術來源與案例細節</td>
          <td>R 現實檢驗</td>
      </tr>
      <tr>
          <td>4</td>
          <td>反向驗證 (Inverse)</td>
          <td>每個結論找反例 / 批評 / 反駁</td>
          <td>防止確認偏誤，揭露悖論</td>
          <td>R 現實檢驗 + A 拉開距離</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="進入下一輪訊號">進入下一輪訊號</h2>
<p>至少 5 條判定訊號，任一觸發即可進入下一輪：</p>
<ol>
<li>該輪新洞察 &lt; 上一輪 50%（經驗閾值）→ 邊際效益遞減（自然進入下輪或收斂）</li>
<li>已涵蓋正反雙面 → 可進入收斂</li>
<li>用戶補充重大反向觀察 → 立即進入反向驗證輪</li>
<li>發現自我參照悖論 → 進入元層次反思輪</li>
<li>出現結論互相衝突 → 進入優先級裁決輪</li>
</ol>
<hr>
<h2 id="邊界條件">邊界條件</h2>
<ul>
<li><strong>不一定要 4 輪</strong>：簡單議題可能 2 輪夠</li>
<li><strong>反向驗證輪幾乎不可省略</strong>（確認偏誤防護）</li>
<li><strong>每輪 8 個搜尋是 網路搜尋（WebSearch）平行查詢上限的經驗值</strong>——可調整</li>
<li><strong>每輪後必暴露偏好</strong>（不偽裝中立），讓用戶可介入糾正方向</li>
</ul>
<hr>
<h2 id="反向驗證實踐範本第-4-輪標準格式">反向驗證實踐範本（第 4 輪標準格式）</h2>
<h3 id="標準表格格式">標準表格格式</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">|----------|-----------|
</span></span><span class="line"><span class="ln">3</span><span class="cl">| [結論 1] | [批評 / 反例 / 反駁 / 限制] |
</span></span><span class="line"><span class="ln">4</span><span class="cl">| [結論 2] | [批評 / 反例 / 反駁 / 限制] |</span></span></code></pre></div><h3 id="8-種反向方向類型">8 種反向方向類型</h3>
<p>每輪反向搜尋必涵蓋至少 4 種，理想涵蓋全部 8 種：</p>
<table>
  <thead>
      <tr>
          <th>反向類型</th>
          <th>識別問題</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. 方法論的學術批評</td>
          <td>此方法論本身有什麼學術反駁？</td>
          <td>MI 缺乏理論基礎、Voss 不適用長期關係</td>
      </tr>
      <tr>
          <td>2. 關鍵案例的反方論點</td>
          <td>案例的另一方怎麼說？</td>
          <td>冤案的警方/檢察官立場</td>
      </tr>
      <tr>
          <td>3. 結論的失效情境</td>
          <td>在什麼情境此結論失效？</td>
          <td>MI 對強制 client 失效</td>
      </tr>
      <tr>
          <td>4. 數字 / 統計的方法論限制</td>
          <td>數字背後的方法論有什麼限制？</td>
          <td>DarkBench 30-61% 的 sampling 限制</td>
      </tr>
      <tr>
          <td>5. 普適性的文化 / 情境限制</td>
          <td>此結論在不同文化 / 情境是否成立？</td>
          <td>Voss 不適用長期關係、自主性（autonomy）是西方偏見</td>
      </tr>
      <tr>
          <td>6. 隱性家長主義（paternalism）警示</td>
          <td>此規則是否本身違反它要保護的價值？</td>
          <td>保護用戶自主性（autonomy）的規則限制了決策者自主性（decision-maker autonomy）</td>
      </tr>
      <tr>
          <td>7. 取捨揭露（trade-off）</td>
          <td>此方案的隱藏代價是什麼？</td>
          <td>迎合（sycophancy）提升滿意度但傷害福祉</td>
      </tr>
      <tr>
          <td>8. 自我參照悖論識別</td>
          <td>此規則的「正確示範」是否本身違反它要禁止的模式？</td>
          <td>反窄框架（narrow framing）本身就是窄框架（narrow framing）</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="單次規則設計的查詢紀錄n1-回顧">單次規則設計的查詢紀錄（n=1 回顧）</h2>
<blockquote>
<p>下表是<strong>一次</strong>規則設計過程的事後回顧、不是可複製的統計規律。數字（搜尋數 / 洞察數 / 累積）來自該次的記憶整理、樣本量 n=1。用它理解「四輪結構大致怎麼推進」、不要當成「每次都會這樣」的基準率 —— 這正是本檔自己倡導的「基本率 &gt; 預測、不把單次回憶當統計」。</p></blockquote>
<table>
  <thead>
      <tr>
          <th>輪次</th>
          <th>搜尋數</th>
          <th>新洞察數</th>
          <th>累積洞察</th>
          <th>關鍵突破</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1（發散）</td>
          <td>8</td>
          <td>6 (A-F)</td>
          <td>6</td>
          <td>Calibrated Questions 雙面性、Confirmshaming 對應</td>
      </tr>
      <tr>
          <td>2（具體化）</td>
          <td>8</td>
          <td>6 (G-L)</td>
          <td>12</td>
          <td>Lifton 8 條件、MI OARS、Anthropic 自承權力不對等</td>
      </tr>
      <tr>
          <td>3（精準化）</td>
          <td>8</td>
          <td>7 (M-S)</td>
          <td>19</td>
          <td>Anthropic 迎合（sycophancy）自證、DarkBench 30-61%</td>
      </tr>
      <tr>
          <td>4（反向）</td>
          <td>8</td>
          <td>8 (U-BB)</td>
          <td><strong>27</strong></td>
          <td><strong>規則 5 是家長主義（paternalism）悖論揭露</strong></td>
      </tr>
  </tbody>
</table>
<p><strong>這次的觀察</strong>（n=1、非通則）：該次第 4 輪反向驗證的洞察數最高、且最尖銳（揭露核心悖論）。這支持「反向輪值得跑」、但「反向輪是主軸」是基於單次的推論、需後續案例累積驗證。</p>
<hr>
<h2 id="用戶介入點記錄">用戶介入點記錄</h2>
<p>多輪查詢過程中，用戶補充常常打到決策者盲點。必記錄：</p>
<table>
  <thead>
      <tr>
          <th>介入時機</th>
          <th>介入內容</th>
          <th>引發效應</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>第 1 輪後</td>
          <td>補充權力不對等 4 例</td>
          <td>暴露決策者窄框架（narrow framing） → 第 2 輪轉向</td>
      </tr>
      <tr>
          <td>規則設計中</td>
          <td>指出「正確示範」也是強勢框架</td>
          <td>機制設計元層次升級</td>
      </tr>
      <tr>
          <td>第 3 輪後</td>
          <td>要求做反向搜尋</td>
          <td>揭露核心悖論</td>
      </tr>
  </tbody>
</table>
<p><strong>用戶補充是設計者盲點的最強發現器</strong>。記錄這些介入點作為未來相似議題的提醒。</p>
<hr>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>症狀</th>
          <th>修正</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>一輪定論</td>
          <td>第一輪結果就下結論</td>
          <td>至少做到第 2 輪</td>
      </tr>
      <tr>
          <td>跳過反向驗證</td>
          <td>直接從第 3 輪進入收斂</td>
          <td>反向輪不可省略</td>
      </tr>
      <tr>
          <td>確認式關鍵字</td>
          <td>第 4 輪關鍵字仍偏向支持原結論</td>
          <td>必須用「批評 / 反駁」明確反向</td>
      </tr>
      <tr>
          <td>忽略用戶補充</td>
          <td>用戶補充被當「離題」</td>
          <td>用戶補充常常是核心訊號</td>
      </tr>
      <tr>
          <td>偽裝中立</td>
          <td>每輪後不暴露偏好</td>
          <td>必暴露偏好讓用戶可介入</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="相關規則">相關規則</h2>
<ul>
<li><a href="/blog/skills/wrap-decision/anti-paternalism/" data-link-title="悖論識別與自我暴露偏好" data-link-desc="WRAP reference：規則設計中的家長主義（paternalism）悖論檢查、自我參照風險、偏好透明化與反操控防護。">anti-paternalism</a> — 第 4 輪揭露的悖論如何處理</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-17
<strong>Source</strong>: 規則設計過程的方法論提煉</p>
]]></content:encoded></item><item><title>決策者快速參考清單（PM Checklist）</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/pm-checklist/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/pm-checklist/</guid><description>&lt;p>WRAP 決策框架的每階段自問清單。在決策時快速掃描使用。&lt;/p>
&lt;hr>
&lt;h2 id="快速模式5-分鐘">快速模式（5 分鐘）&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">[錨點] 誰是客戶？當前版本核心目標？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> → 這個問題影響核心目標嗎？否 → 建任務延後
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">[W] 我有幾個選項？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> → 只有 1 個？→ 強制再找 2 個
&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"> → 選項有實質差異嗎？→ 沒有 → 假選項，重新擴增
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">[R 核心] 這類問題的常見解法和成功率？（基本率 &amp;gt; 預測）
&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">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">[A 前置] 框架概括測試：能否用一句話概括所有方案？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> → 能 → 回 W 擴增多元選項
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> → 否 → 進 A 核心
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">[A 核心] 投入這個問題的時間，會擠壓哪個更重要的目標？（機會成本）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> → 列出放棄什麼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">[決定] 選項消失測試：以上選項全部被移除，還能怎麼做？&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="完整模式15-30-分鐘">完整模式（15-30 分鐘）&lt;/h2>
&lt;p>適用：偏離核心、重大決策&lt;/p>
&lt;h3 id="0-錨點確認">0. 錨點確認&lt;/h3>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 誰是我們的客戶？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 當前版本的核心目標是什麼？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 這個問題影響核心客戶/核心目標嗎？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 如果不影響：建提案/任務延後，回到核心任務&lt;/li>
&lt;/ul>
&lt;h3 id="w--擴增選項">W — 擴增選項&lt;/h3>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 我有幾個選項？（至少 3 個有實質差異的）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 當前專案有沒有類似解法？（grep / LSP）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 社群有沒有人解決過？（GitHub Issues / 文件）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 其他領域有沒有類比方案？（AI 知識 / 網路搜尋（WebSearch））&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 選項消失測試：以上選項全部被移除，還能怎麼做？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 假選項偵測：選項之間是不同途徑，還是同一途徑的參數微調？&lt;/li>
&lt;/ul>
&lt;h3 id="r--現實檢驗">R — 現實檢驗&lt;/h3>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 這類問題的常見解法有哪些？成功率？（基本率）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 需要什麼事證才能證明這個方法可行？（事證問題）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 有沒有辦法用最小成本驗證假設？5 分鐘內能得到答案嗎？（試水溫）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 搜尋了具體案例嗎？讀了內文不只標題？（近距離檢視（Zoom In））&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 最強版本論證（Steelman）：能闡明被放棄選項的優點嗎？能列出選定方案的 3 個缺點嗎？&lt;/li>
&lt;/ul>
&lt;h3 id="a--拉開距離">A — 拉開距離&lt;/h3>
&lt;p>&lt;strong>前置強制檢查&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 框架概括測試：能否用一句話概括所有候選方案？「能」→ 選項多元性不足，回 W&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 反面框架識別：問題的反面框架是什麼？（未列出即強制停下列出）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 反向思考（Consider the Opposite）：若我相信的正好相反會怎樣？能提出有力相反論述 → 重新定義問題後從 W 重跑&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>正式 A 階段&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 每個選項旁列出機會成本了嗎？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 這個決策服務於哪個優先事項？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 投入這個問題的時間，會擠壓哪個更重要的目標？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 不值得投入的項目，是否已記錄到提案系統或任務延後？&lt;/li>
&lt;/ul>
&lt;h3 id="p--準備好犯錯">P — 準備好犯錯&lt;/h3>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 行前預想：假設失敗了，列出 3 個最可能原因（任一 &amp;gt; 50% → 重新評估）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 未來區間：最好結果？最壞結果？兩端都能接受？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 安全係數：預估時間 x 1.3-1.5&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 回退計畫：失敗怎麼回退？回退成本是多少？&lt;/li>
&lt;/ul>
&lt;h3 id="5-絆腳索設定">5. 絆腳索設定&lt;/h3>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 設定期限：非核心問題最多花多少分鐘？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 設定失敗門檻：幾次失敗後切換方向？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 設定偏離警報：什麼信號代表已偏離核心目標？&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="決策品質自測">決策品質自測&lt;/h2>
&lt;p>完成 WRAP 後，用以下問題自測：&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>我考慮了幾個有實質差異的選項？&lt;/td>
 &lt;td>&amp;gt;= 3 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>我搜尋了社群/文件嗎？&lt;/td>
 &lt;td>是&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>我能闡明反對意見嗎？&lt;/td>
 &lt;td>是&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>我列出了機會成本嗎？&lt;/td>
 &lt;td>是&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>我做了行前預想嗎？&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>4/6 以上通過 → 決策品質足夠。
3/6 以下 → 回到對應階段補充。&lt;/p></description><content:encoded><![CDATA[<p>WRAP 決策框架的每階段自問清單。在決策時快速掃描使用。</p>
<hr>
<h2 id="快速模式5-分鐘">快速模式（5 分鐘）</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">[W] 我有幾個選項？
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   → 只有 1 個？→ 強制再找 2 個
</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">   → 選項有實質差異嗎？→ 沒有 → 假選項，重新擴增
</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">[R 核心] 這類問題的常見解法和成功率？（基本率 &gt; 預測）
</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">[A 前置] 框架概括測試：能否用一句話概括所有方案？
</span></span><span class="line"><span class="ln">13</span><span class="cl">   → 能 → 回 W 擴增多元選項
</span></span><span class="line"><span class="ln">14</span><span class="cl">   → 否 → 進 A 核心
</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">[A 核心] 投入這個問題的時間，會擠壓哪個更重要的目標？（機會成本）
</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="完整模式15-30-分鐘">完整模式（15-30 分鐘）</h2>
<p>適用：偏離核心、重大決策</p>
<h3 id="0-錨點確認">0. 錨點確認</h3>
<ul>
<li><input disabled="" type="checkbox"> 誰是我們的客戶？</li>
<li><input disabled="" type="checkbox"> 當前版本的核心目標是什麼？</li>
<li><input disabled="" type="checkbox"> 這個問題影響核心客戶/核心目標嗎？</li>
<li><input disabled="" type="checkbox"> 如果不影響：建提案/任務延後，回到核心任務</li>
</ul>
<h3 id="w--擴增選項">W — 擴增選項</h3>
<ul>
<li><input disabled="" type="checkbox"> 我有幾個選項？（至少 3 個有實質差異的）</li>
<li><input disabled="" type="checkbox"> 當前專案有沒有類似解法？（grep / LSP）</li>
<li><input disabled="" type="checkbox"> 社群有沒有人解決過？（GitHub Issues / 文件）</li>
<li><input disabled="" type="checkbox"> 其他領域有沒有類比方案？（AI 知識 / 網路搜尋（WebSearch））</li>
<li><input disabled="" type="checkbox"> 選項消失測試：以上選項全部被移除，還能怎麼做？</li>
<li><input disabled="" type="checkbox"> 假選項偵測：選項之間是不同途徑，還是同一途徑的參數微調？</li>
</ul>
<h3 id="r--現實檢驗">R — 現實檢驗</h3>
<ul>
<li><input disabled="" type="checkbox"> 這類問題的常見解法有哪些？成功率？（基本率）</li>
<li><input disabled="" type="checkbox"> 需要什麼事證才能證明這個方法可行？（事證問題）</li>
<li><input disabled="" type="checkbox"> 有沒有辦法用最小成本驗證假設？5 分鐘內能得到答案嗎？（試水溫）</li>
<li><input disabled="" type="checkbox"> 搜尋了具體案例嗎？讀了內文不只標題？（近距離檢視（Zoom In））</li>
<li><input disabled="" type="checkbox"> 最強版本論證（Steelman）：能闡明被放棄選項的優點嗎？能列出選定方案的 3 個缺點嗎？</li>
</ul>
<h3 id="a--拉開距離">A — 拉開距離</h3>
<p><strong>前置強制檢查</strong>：</p>
<ul>
<li><input disabled="" type="checkbox"> 框架概括測試：能否用一句話概括所有候選方案？「能」→ 選項多元性不足，回 W</li>
<li><input disabled="" type="checkbox"> 反面框架識別：問題的反面框架是什麼？（未列出即強制停下列出）</li>
<li><input disabled="" type="checkbox"> 反向思考（Consider the Opposite）：若我相信的正好相反會怎樣？能提出有力相反論述 → 重新定義問題後從 W 重跑</li>
</ul>
<p><strong>正式 A 階段</strong>：</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"> 不值得投入的項目，是否已記錄到提案系統或任務延後？</li>
</ul>
<h3 id="p--準備好犯錯">P — 準備好犯錯</h3>
<ul>
<li><input disabled="" type="checkbox"> 行前預想：假設失敗了，列出 3 個最可能原因（任一 &gt; 50% → 重新評估）</li>
<li><input disabled="" type="checkbox"> 未來區間：最好結果？最壞結果？兩端都能接受？</li>
<li><input disabled="" type="checkbox"> 安全係數：預估時間 x 1.3-1.5</li>
<li><input disabled="" type="checkbox"> 回退計畫：失敗怎麼回退？回退成本是多少？</li>
</ul>
<h3 id="5-絆腳索設定">5. 絆腳索設定</h3>
<ul>
<li><input disabled="" type="checkbox"> 設定期限：非核心問題最多花多少分鐘？</li>
<li><input disabled="" type="checkbox"> 設定失敗門檻：幾次失敗後切換方向？</li>
<li><input disabled="" type="checkbox"> 設定偏離警報：什麼信號代表已偏離核心目標？</li>
</ul>
<hr>
<h2 id="決策品質自測">決策品質自測</h2>
<p>完成 WRAP 後，用以下問題自測：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>通過標準</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>我考慮了幾個有實質差異的選項？</td>
          <td>&gt;= 3 個</td>
      </tr>
      <tr>
          <td>我搜尋了社群/文件嗎？</td>
          <td>是</td>
      </tr>
      <tr>
          <td>我能闡明反對意見嗎？</td>
          <td>是</td>
      </tr>
      <tr>
          <td>我列出了機會成本嗎？</td>
          <td>是</td>
      </tr>
      <tr>
          <td>我做了行前預想嗎？</td>
          <td>是</td>
      </tr>
      <tr>
          <td>我有回退計畫嗎？</td>
          <td>是</td>
      </tr>
  </tbody>
</table>
<p>4/6 以上通過 → 決策品質足夠。
3/6 以下 → 回到對應階段補充。</p>
<hr>
<p><strong>Last Updated</strong>: 2026-04-16
<strong>Version</strong>: 1.1.0</p>
]]></content:encoded></item><item><title>悖論識別與自我暴露偏好</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/anti-paternalism/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/anti-paternalism/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>本檔位置&lt;/strong>：A 階段 + P 階段擴充。WRAP 既有的「拉開距離」與「準備犯錯」原則本身不規範&lt;strong>規則自身的悖論檢查&lt;/strong>與&lt;strong>設計者偏好透明化&lt;/strong>，本檔補充。&lt;/p>
&lt;p>&lt;strong>來源實證&lt;/strong>：規則設計過程中，第 4 輪反向驗證揭露「保護用戶自主性（autonomy）的規則可能限制代理人自主性（autonomy）」的核心悖論。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="為什麼需要悖論識別">為什麼需要悖論識別&lt;/h2>
&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>自我參照悖論&lt;/td>
 &lt;td>反窄框架（narrow framing）規則本身就是窄框架（narrow framing）&lt;/td>
 &lt;td>規則自我證成失敗&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>隱性家長主義（paternalism）&lt;/td>
 &lt;td>為保護 X 而限制 Y 的自主性（autonomy）&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>強推到不適用情境&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="悖論識別檢查清單">悖論識別檢查清單&lt;/h2>
&lt;p>設計規則 / 流程 / 系統時，必檢查（至少 4 條）：&lt;/p>
&lt;h3 id="檢查-1自我參照測試">檢查 1：自我參照測試&lt;/h3>
&lt;blockquote>
&lt;p>此規則的「正確示範」是否本身就是它要禁止的模式？&lt;/p>&lt;/blockquote>
&lt;p>&lt;strong>範例&lt;/strong>：規則禁止「強勢敘事」，但規則自己用偽中立框架預先定義問題性質，這就是自我參照悖論。&lt;/p>
&lt;h3 id="檢查-2善意家長主義benevolent-paternalism4-條件測試">檢查 2：善意家長主義（benevolent paternalism）4 條件測試&lt;/h3>
&lt;blockquote>
&lt;p>此規則是否符合 Frontiers AI 研究提出的「善意家長主義（benevolent paternalism）4 條件」？&lt;/p>&lt;/blockquote>
&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>2. 介入大概率防止傷害&lt;/td>
 &lt;td>此規則的介入是否確實能防止傷害？（非空想）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3. 利益大於風險&lt;/td>
 &lt;td>規則帶來的利益是否大於它的限制成本？&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4. 最小限制介入&lt;/td>
 &lt;td>是否還有更輕的介入方式達到同樣效果？&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>4 條件全通過才能稱為「正當的家長主義（paternalism）」&lt;/strong>。任一不通過即為過度家長主義（paternalism）。&lt;/p>
&lt;h3 id="檢查-3權力對偶性測試">檢查 3：權力對偶性測試&lt;/h3>
&lt;blockquote>
&lt;p>此規則是否本身違反它要保護的價值？&lt;/p>&lt;/blockquote>
&lt;p>&lt;strong>範例&lt;/strong>：保護用戶自主性（autonomy）的規則限制了 Claude（也是利害關係人（stakeholder））的自主性（autonomy）。為了 X 反而傷害 X。&lt;/p>
&lt;p>&lt;strong>處理&lt;/strong>：在規則中明示「本規則是動態平衡」並提供「用戶可覆蓋」機制。&lt;/p>
&lt;h3 id="檢查-4設計者立場透明化測試">檢查 4：設計者立場透明化測試&lt;/h3>
&lt;blockquote>
&lt;p>此規則的設計者立場是否被透明化？&lt;/p>&lt;/blockquote>
&lt;p>設計者本身是利害關係人（stakeholder），不可能完全中立。必須明示：&lt;/p>
&lt;ul>
&lt;li>規則由誰設計？&lt;/li>
&lt;li>設計者的利益偏好為何？&lt;/li>
&lt;li>用戶有什麼權利覆蓋設計者偏好？&lt;/li>
&lt;/ul>
&lt;h3 id="檢查-5用戶覆蓋機制測試">檢查 5：用戶覆蓋機制測試&lt;/h3>
&lt;blockquote>
&lt;p>此規則是否預留「用戶可覆蓋」機制？&lt;/p>&lt;/blockquote>
&lt;p>沒有覆蓋機制的規則 = 思想改造（thought reform）。對應 Lifton 8 條件第 7「教義凌駕個人（Doctrine over Person）」。&lt;/p>
&lt;hr>
&lt;h2 id="自我暴露偏好實踐">自我暴露偏好實踐&lt;/h2>
&lt;h3 id="為什麼必要">為什麼必要&lt;/h3>
&lt;p>提供建議時假裝中立 = 隱性家長主義（paternalism）。Voss 自陳「one person&amp;rsquo;s influence is another person&amp;rsquo;s manipulation」——影響力的本質取決於是否被透明化。&lt;/p>
&lt;h3 id="4-個實踐維度">4 個實踐維度&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>實踐&lt;/th>
 &lt;th>禁止&lt;/th>
 &lt;th>正確&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>暴露偏好&lt;/td>
 &lt;td>假裝中立提問&lt;/td>
 &lt;td>「我傾向 X，理由 Y」&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>「我可能漏掉的角度有 Z」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>標記偏誤防護&lt;/td>
 &lt;td>標成推薦（Recommended）&lt;/td>
 &lt;td>改為「我目前的猜測」或不標&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="為什麼推薦標記recommended是暗黑模式dark-pattern">為什麼推薦標記（Recommended）是暗黑模式（dark pattern）&lt;/h3>
&lt;ul>
&lt;li>暗黑模式（dark patterns）中 confirmshaming 把「拒絕」框定為負面選擇&lt;/li>
&lt;li>推薦標記（Recommended）隱性把「未推薦」貶為次優&lt;/li>
&lt;li>結合「位置偏誤」（推薦選項放第一）效果加倍&lt;/li>
&lt;li>DarkBench 已將此類設計列為 LLM 暗黑模式（dark pattern）&lt;/li>
&lt;/ul>
&lt;h3 id="自我暴露範本">自我暴露範本&lt;/h3>
&lt;p>提供建議時的標準格式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="gu">## 我的偏好暴露（不標推薦（Recommended））
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">| 方向 | 我的偏好 | 暴露的理由與風險 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">|------|---------|---------------|
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">| 方案 A | 強偏好 | 理由是 [Y]；但這偏好可能反映我的 [Z] 立場 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">| 方案 B | 中等 | 理由是 [Y2] |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">| 方案 C | 低 | 理由是 [Y3]，但你可能因 [W] 反而選此 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">| 反方向 | 開放 | 我可能未考慮的角度 |&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&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>偽中立提問&lt;/td>
 &lt;td>「你覺得哪個好？」實際心裡有偏好&lt;/td>
 &lt;td>先暴露偏好「我傾向 X」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>推薦標記（Recommended）&lt;/td>
 &lt;td>給選項時標成推薦（Recommended）&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>明示「我可能漏掉的是 Z」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>結論先行&lt;/td>
 &lt;td>先給結論再補理由&lt;/td>
 &lt;td>先列推理鏈再到結論&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="與既有-wrap-章節的關係">與既有 WRAP 章節的關係&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>A 階段「確認偏誤防護前置強制檢查」&lt;/td>
 &lt;td>補充「規則本身的悖論檢查」維度&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>P 階段「行前預想（Premortem）」&lt;/td>
 &lt;td>補充「設計者立場透明化」實踐&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>W 階段「反向思考（Consider the Opposite）」&lt;/td>
 &lt;td>補充「自我參照悖論」識別&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>R 階段「最強版本論證（Steelman）」&lt;/td>
 &lt;td>補充「規則自證測試」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="應用情境">應用情境&lt;/h2>
&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>對 5 條檢查清單逐項自檢&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>修改既有規則&lt;/td>
 &lt;td>規則修改前&lt;/td>
 &lt;td>確認修改是否觸發新悖論&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>掛鉤（Hook）/ Skill 設計&lt;/td>
 &lt;td>寫前 / 寫後&lt;/td>
 &lt;td>家長主義（paternalism）4 條件測試&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>悖論識別優先&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="相關規則">相關規則&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/iterative-research/" data-link-title="多輪迭代查詢方法論" data-link-desc="WRAP reference：用發散、具體化、精準化、反向驗證四輪結構支撐深度查詢與反向驗證。">iterative-research&lt;/a> — 多輪迭代如何揭露悖論&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="學術依據">學術依據&lt;/h2>
&lt;p>以下為概念來源；本檔引用的是其觀點框架、非逐項實證複製。使用時若要當論據，建議回查原文確認語境與年份。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p><strong>本檔位置</strong>：A 階段 + P 階段擴充。WRAP 既有的「拉開距離」與「準備犯錯」原則本身不規範<strong>規則自身的悖論檢查</strong>與<strong>設計者偏好透明化</strong>，本檔補充。</p>
<p><strong>來源實證</strong>：規則設計過程中，第 4 輪反向驗證揭露「保護用戶自主性（autonomy）的規則可能限制代理人自主性（autonomy）」的核心悖論。</p></blockquote>
<hr>
<h2 id="為什麼需要悖論識別">為什麼需要悖論識別</h2>
<h3 id="規則設計的常見陷阱">規則設計的常見陷阱</h3>
<table>
  <thead>
      <tr>
          <th>陷阱類型</th>
          <th>範例</th>
          <th>後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>自我參照悖論</td>
          <td>反窄框架（narrow framing）規則本身就是窄框架（narrow framing）</td>
          <td>規則自我證成失敗</td>
      </tr>
      <tr>
          <td>隱性家長主義（paternalism）</td>
          <td>為保護 X 而限制 Y 的自主性（autonomy）</td>
          <td>移轉問題不解決問題</td>
      </tr>
      <tr>
          <td>目的-手段倒置</td>
          <td>為了禁止暗示而暗示「應該透明」</td>
          <td>違反規則本意</td>
      </tr>
      <tr>
          <td>萬能框架幻覺</td>
          <td>認為一個規則涵蓋所有情境</td>
          <td>強推到不適用情境</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="悖論識別檢查清單">悖論識別檢查清單</h2>
<p>設計規則 / 流程 / 系統時，必檢查（至少 4 條）：</p>
<h3 id="檢查-1自我參照測試">檢查 1：自我參照測試</h3>
<blockquote>
<p>此規則的「正確示範」是否本身就是它要禁止的模式？</p></blockquote>
<p><strong>範例</strong>：規則禁止「強勢敘事」，但規則自己用偽中立框架預先定義問題性質，這就是自我參照悖論。</p>
<h3 id="檢查-2善意家長主義benevolent-paternalism4-條件測試">檢查 2：善意家長主義（benevolent paternalism）4 條件測試</h3>
<blockquote>
<p>此規則是否符合 Frontiers AI 研究提出的「善意家長主義（benevolent paternalism）4 條件」？</p></blockquote>
<table>
  <thead>
      <tr>
          <th>條件</th>
          <th>通過標準</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. 實質可預防的傷害</td>
          <td>此規則防護的傷害是否真實且可預防？</td>
      </tr>
      <tr>
          <td>2. 介入大概率防止傷害</td>
          <td>此規則的介入是否確實能防止傷害？（非空想）</td>
      </tr>
      <tr>
          <td>3. 利益大於風險</td>
          <td>規則帶來的利益是否大於它的限制成本？</td>
      </tr>
      <tr>
          <td>4. 最小限制介入</td>
          <td>是否還有更輕的介入方式達到同樣效果？</td>
      </tr>
  </tbody>
</table>
<p><strong>4 條件全通過才能稱為「正當的家長主義（paternalism）」</strong>。任一不通過即為過度家長主義（paternalism）。</p>
<h3 id="檢查-3權力對偶性測試">檢查 3：權力對偶性測試</h3>
<blockquote>
<p>此規則是否本身違反它要保護的價值？</p></blockquote>
<p><strong>範例</strong>：保護用戶自主性（autonomy）的規則限制了 Claude（也是利害關係人（stakeholder））的自主性（autonomy）。為了 X 反而傷害 X。</p>
<p><strong>處理</strong>：在規則中明示「本規則是動態平衡」並提供「用戶可覆蓋」機制。</p>
<h3 id="檢查-4設計者立場透明化測試">檢查 4：設計者立場透明化測試</h3>
<blockquote>
<p>此規則的設計者立場是否被透明化？</p></blockquote>
<p>設計者本身是利害關係人（stakeholder），不可能完全中立。必須明示：</p>
<ul>
<li>規則由誰設計？</li>
<li>設計者的利益偏好為何？</li>
<li>用戶有什麼權利覆蓋設計者偏好？</li>
</ul>
<h3 id="檢查-5用戶覆蓋機制測試">檢查 5：用戶覆蓋機制測試</h3>
<blockquote>
<p>此規則是否預留「用戶可覆蓋」機制？</p></blockquote>
<p>沒有覆蓋機制的規則 = 思想改造（thought reform）。對應 Lifton 8 條件第 7「教義凌駕個人（Doctrine over Person）」。</p>
<hr>
<h2 id="自我暴露偏好實踐">自我暴露偏好實踐</h2>
<h3 id="為什麼必要">為什麼必要</h3>
<p>提供建議時假裝中立 = 隱性家長主義（paternalism）。Voss 自陳「one person&rsquo;s influence is another person&rsquo;s manipulation」——影響力的本質取決於是否被透明化。</p>
<h3 id="4-個實踐維度">4 個實踐維度</h3>
<table>
  <thead>
      <tr>
          <th>實踐</th>
          <th>禁止</th>
          <th>正確</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>暴露偏好</td>
          <td>假裝中立提問</td>
          <td>「我傾向 X，理由 Y」</td>
      </tr>
      <tr>
          <td>暴露推理鏈</td>
          <td>只給結論</td>
          <td>列出推理步驟讓用戶可追溯</td>
      </tr>
      <tr>
          <td>暴露盲點</td>
          <td>假裝完整考慮</td>
          <td>「我可能漏掉的角度有 Z」</td>
      </tr>
      <tr>
          <td>標記偏誤防護</td>
          <td>標成推薦（Recommended）</td>
          <td>改為「我目前的猜測」或不標</td>
      </tr>
  </tbody>
</table>
<h3 id="為什麼推薦標記recommended是暗黑模式dark-pattern">為什麼推薦標記（Recommended）是暗黑模式（dark pattern）</h3>
<ul>
<li>暗黑模式（dark patterns）中 confirmshaming 把「拒絕」框定為負面選擇</li>
<li>推薦標記（Recommended）隱性把「未推薦」貶為次優</li>
<li>結合「位置偏誤」（推薦選項放第一）效果加倍</li>
<li>DarkBench 已將此類設計列為 LLM 暗黑模式（dark pattern）</li>
</ul>
<h3 id="自我暴露範本">自我暴露範本</h3>
<p>提供建議時的標準格式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">## 我的偏好暴露（不標推薦（Recommended））
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></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></span><span class="line"><span class="ln">5</span><span class="cl">| 方案 A | 強偏好 | 理由是 [Y]；但這偏好可能反映我的 [Z] 立場 |
</span></span><span class="line"><span class="ln">6</span><span class="cl">| 方案 B | 中等 | 理由是 [Y2] |
</span></span><span class="line"><span class="ln">7</span><span class="cl">| 方案 C | 低 | 理由是 [Y3]，但你可能因 [W] 反而選此 |
</span></span><span class="line"><span class="ln">8</span><span class="cl">| 反方向 | 開放 | 我可能未考慮的角度 |</span></span></code></pre></div><h3 id="反模式">反模式</h3>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>症狀</th>
          <th>修正</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>偽中立提問</td>
          <td>「你覺得哪個好？」實際心裡有偏好</td>
          <td>先暴露偏好「我傾向 X」</td>
      </tr>
      <tr>
          <td>推薦標記（Recommended）</td>
          <td>給選項時標成推薦（Recommended）</td>
          <td>改為「我目前的猜測」或不標</td>
      </tr>
      <tr>
          <td>位置偏誤</td>
          <td>推薦選項放第一個</td>
          <td>刻意放第二、第三</td>
      </tr>
      <tr>
          <td>假裝全知</td>
          <td>不承認盲點</td>
          <td>明示「我可能漏掉的是 Z」</td>
      </tr>
      <tr>
          <td>結論先行</td>
          <td>先給結論再補理由</td>
          <td>先列推理鏈再到結論</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="與既有-wrap-章節的關係">與既有 WRAP 章節的關係</h2>
<table>
  <thead>
      <tr>
          <th>既有章節</th>
          <th>本檔補充</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>A 階段「確認偏誤防護前置強制檢查」</td>
          <td>補充「規則本身的悖論檢查」維度</td>
      </tr>
      <tr>
          <td>P 階段「行前預想（Premortem）」</td>
          <td>補充「設計者立場透明化」實踐</td>
      </tr>
      <tr>
          <td>W 階段「反向思考（Consider the Opposite）」</td>
          <td>補充「自我參照悖論」識別</td>
      </tr>
      <tr>
          <td>R 階段「最強版本論證（Steelman）」</td>
          <td>補充「規則自證測試」</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="應用情境">應用情境</h2>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>觸發</th>
          <th>應用</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>設計新規則</td>
          <td>規則草擬完成</td>
          <td>對 5 條檢查清單逐項自檢</td>
      </tr>
      <tr>
          <td>修改既有規則</td>
          <td>規則修改前</td>
          <td>確認修改是否觸發新悖論</td>
      </tr>
      <tr>
          <td>掛鉤（Hook）/ Skill 設計</td>
          <td>寫前 / 寫後</td>
          <td>家長主義（paternalism）4 條件測試</td>
      </tr>
      <tr>
          <td>提供建議</td>
          <td>每次回應</td>
          <td>自我暴露偏好範本</td>
      </tr>
      <tr>
          <td>提案評估</td>
          <td>評估提案文件</td>
          <td>悖論識別優先</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="相關規則">相關規則</h2>
<ul>
<li><a href="/blog/skills/wrap-decision/iterative-research/" data-link-title="多輪迭代查詢方法論" data-link-desc="WRAP reference：用發散、具體化、精準化、反向驗證四輪結構支撐深度查詢與反向驗證。">iterative-research</a> — 多輪迭代如何揭露悖論</li>
</ul>
<hr>
<h2 id="學術依據">學術依據</h2>
<p>以下為概念來源；本檔引用的是其觀點框架、非逐項實證複製。使用時若要當論據，建議回查原文確認語境與年份。</p>
<ul>
<li><strong>善意家長主義（benevolent paternalism）4 條件框架</strong> — 來源為 AI 倫理文獻（Frontiers 期刊系列）；本檔未綁定特定論文，引用時請回查。</li>
<li><strong>Anthropic Constitutional AI</strong>：「balance user wellbeing against user autonomy and excessive paternalism」（Anthropic 公開 CAI / HHH 論述）。</li>
<li><strong>Lifton《Thought Reform and the Psychology of Totalism》(1961) 8 條件</strong>：第 7「教義凌駕個人（Doctrine over Person）」作為反例。</li>
<li><strong>Chris Voss《Never Split the Difference》</strong>：「intent decides」（influence vs manipulation 的區分）自陳。</li>
<li><strong>DarkBench 30-61%</strong>（iterative-research 引）：為該基準的暗黑模式偵測率區間、依模型 / 指標而異，引用時需標明指標與樣本限制。</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-17
<strong>Source</strong>: 規則設計過程的悖論揭露與自我暴露實踐</p>
]]></content:encoded></item><item><title>偽擴增選項防護（Pseudo Widen Guard）</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-pseudo-widen-guard/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/integration-patterns-pseudo-widen-guard/</guid><description>&lt;p>偽擴增選項（pseudo-Widen）防護的責任是確認選項多元性發生在假設層級，而不只是實作手段層級。它特別適用於根因分析、架構決策與規則設計。&lt;/p>
&lt;hr>
&lt;h2 id="核心判準">核心判準&lt;/h2>
&lt;p>真正的擴增選項（Widen）會提出不同根因假設或不同達成路徑。偽擴增選項（pseudo-Widen）只是在同一假設下列出多個變體，表面上有選項，實際上仍被單一框架鎖住。&lt;/p>
&lt;hr>
&lt;h2 id="步驟">步驟&lt;/h2>
&lt;h3 id="1-寫出目前假設">1. 寫出目前假設&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;/code>&lt;/pre>&lt;/div>&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;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-檢查選項層級">3. 檢查選項層級&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>檢查&lt;/th>
 &lt;th>通過標準&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>根因多元&lt;/td>
 &lt;td>選項來自不同根因假設&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>路徑多元&lt;/td>
 &lt;td>選項包含新增、改造既有、零工具或流程調整&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>證據多元&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;h3 id="4-現實檢驗reality-test">4. 現實檢驗（Reality Test）&lt;/h3>
&lt;p>每個候選假設至少要有一個可驗證訊號。缺少可驗證訊號的選項保留為待驗假設，並在補齊證據後再進入方案比較。&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>所有選項能用一句話概括&lt;/td>
 &lt;td>選項仍在同一框架內&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>所有選項只差工具或參數&lt;/td>
 &lt;td>還沒碰到根因層&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>所有選項需要同一份證據&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;hr>
&lt;h2 id="可複製提示">可複製提示&lt;/h2>





&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">先做偽擴增選項（pseudo-Widen）檢查：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">1. 我現在假設的根因是什麼？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">2. 相反假設是什麼？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">3. 目前選項是否都接受同一根因？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">4. 哪個證據能讓我放棄目前最相信的假設？&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>&lt;strong>Last Updated&lt;/strong>: 2026-04-30
&lt;strong>Version&lt;/strong>: 2.0.0 — 可攜偽擴增選項（pseudo-Widen）防護模板。&lt;/p></description><content:encoded><![CDATA[<p>偽擴增選項（pseudo-Widen）防護的責任是確認選項多元性發生在假設層級，而不只是實作手段層級。它特別適用於根因分析、架構決策與規則設計。</p>
<hr>
<h2 id="核心判準">核心判準</h2>
<p>真正的擴增選項（Widen）會提出不同根因假設或不同達成路徑。偽擴增選項（pseudo-Widen）只是在同一假設下列出多個變體，表面上有選項，實際上仍被單一框架鎖住。</p>
<hr>
<h2 id="步驟">步驟</h2>
<h3 id="1-寫出目前假設">1. 寫出目前假設</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></code></pre></div><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></code></pre></div><h3 id="3-檢查選項層級">3. 檢查選項層級</h3>
<table>
  <thead>
      <tr>
          <th>檢查</th>
          <th>通過標準</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>根因多元</td>
          <td>選項來自不同根因假設</td>
      </tr>
      <tr>
          <td>路徑多元</td>
          <td>選項包含新增、改造既有、零工具或流程調整</td>
      </tr>
      <tr>
          <td>證據多元</td>
          <td>每個選項需要不同證據驗證</td>
      </tr>
      <tr>
          <td>回退多元</td>
          <td>每個選項的失敗與回退方式不同</td>
      </tr>
  </tbody>
</table>
<h3 id="4-現實檢驗reality-test">4. 現實檢驗（Reality Test）</h3>
<p>每個候選假設至少要有一個可驗證訊號。缺少可驗證訊號的選項保留為待驗假設，並在補齊證據後再進入方案比較。</p>
<hr>
<h2 id="警告信號">警告信號</h2>
<table>
  <thead>
      <tr>
          <th>信號</th>
          <th>意義</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>所有選項能用一句話概括</td>
          <td>選項仍在同一框架內</td>
      </tr>
      <tr>
          <td>所有選項只差工具或參數</td>
          <td>還沒碰到根因層</td>
      </tr>
      <tr>
          <td>所有選項需要同一份證據</td>
          <td>證據路徑缺乏多元性</td>
      </tr>
      <tr>
          <td>被使用者重述問題後整份分析失效</td>
          <td>原問題框架太窄</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="可複製提示">可複製提示</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">先做偽擴增選項（pseudo-Widen）檢查：
</span></span><span class="line"><span class="ln">2</span><span class="cl">1. 我現在假設的根因是什麼？
</span></span><span class="line"><span class="ln">3</span><span class="cl">2. 相反假設是什麼？
</span></span><span class="line"><span class="ln">4</span><span class="cl">3. 目前選項是否都接受同一根因？
</span></span><span class="line"><span class="ln">5</span><span class="cl">4. 哪個證據能讓我放棄目前最相信的假設？</span></span></code></pre></div><hr>
<p><strong>Last Updated</strong>: 2026-04-30
<strong>Version</strong>: 2.0.0 — 可攜偽擴增選項（pseudo-Widen）防護模板。</p>
]]></content:encoded></item><item><title>絆腳索類型目錄</title><link>https://tarrragon.github.io/blog/skills/wrap-decision/tripwire-catalog/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/wrap-decision/tripwire-catalog/</guid><description>&lt;p>WRAP 框架的持續監控機制。絆腳索不告訴你該怎麼做，而是提醒你「你是有選擇的」。&lt;/p>
&lt;blockquote>
&lt;p>來源：《零偏見決斷法》— 自動駕駛模式讓人忽略機會、堅持註定失敗的計畫。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="絆腳索總覽">絆腳索總覽&lt;/h2>
&lt;h3 id="防護型防止壞事">防護型（防止壞事）&lt;/h3>
&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>&lt;strong>期限型&lt;/strong>&lt;/td>
 &lt;td>非核心問題花 &amp;gt; 15 分鐘&lt;/td>
 &lt;td>建任務延後，回到核心&lt;/td>
 &lt;td>自律 + 計時機制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>失敗型&lt;/strong>&lt;/td>
 &lt;td>同一修改連續 2 次失敗&lt;/td>
 &lt;td>搜尋社群或換方向&lt;/td>
 &lt;td>失敗計數偵測&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>偏離型&lt;/strong>&lt;/td>
 &lt;td>連續 2+ 個任務單位不在當前迭代目標&lt;/td>
 &lt;td>回到核心任務&lt;/td>
 &lt;td>任務追蹤系統統計&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>回退型&lt;/strong>&lt;/td>
 &lt;td>已回退過一次修改&lt;/td>
 &lt;td>完全停止，換方向&lt;/td>
 &lt;td>自律&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>嘗試型&lt;/strong>&lt;/td>
 &lt;td>同一問題嘗試 2 次修改&lt;/td>
 &lt;td>向外求解（社群/AI）&lt;/td>
 &lt;td>自律&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>決策路徑型&lt;/strong>（決策判斷干擾子型）&lt;/td>
 &lt;td>決策流程中跳過自檢的自動駕駛（autopilot）；4 個子型：CLI 自動駕駛（autopilot） / 既有結論錨定（Anchor） / 草率改規則 / 多步驟成功率盲點&lt;/td>
 &lt;td>依子型對應動作（見下方詳表）&lt;/td>
 &lt;td>自律 + 掛鉤（Hook）偵測（部分）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="決策路徑型子型詳表">決策路徑型子型詳表&lt;/h3>
&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>&lt;strong>3.1 CLI / 規則自動駕駛（autopilot）&lt;/strong>&lt;/td>
 &lt;td>CLI 撞錯後立即重試或猜變體&lt;/td>
 &lt;td>強制「撞錯即 &lt;code>--help&lt;/code>」規則&lt;/td>
 &lt;td>查文件後再試&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>3.2 既有結論錨定（Anchor）&lt;/strong>&lt;/td>
 &lt;td>WRAP W 階段選項能一句話概括 / 全指向同一根因&lt;/td>
 &lt;td>強制反向思考（Consider the Opposite）&lt;/td>
 &lt;td>重新定義問題後 W 重跑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>3.3 規則失敗草率改規則&lt;/strong>&lt;/td>
 &lt;td>失敗第一反應「改規則」&lt;/td>
 &lt;td>先重試 2 次才改&lt;/td>
 &lt;td>挖根因再決定改規則或改行為&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>3.4 多步驟成功率盲點&lt;/strong>&lt;/td>
 &lt;td>多步驟計畫中所有中間步驟都預測成功&lt;/td>
 &lt;td>WRAP R 階段基本率檢查&lt;/td>
 &lt;td>每步獨立驗證&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;blockquote>
&lt;p>本表提供 WRAP 觸發時的查表入口；各專案可把這些子型映射到自己的掛鉤（Hook）或任務系統。&lt;/p>&lt;/blockquote>
&lt;h3 id="正面型捕捉好事">正面型（捕捉好事）&lt;/h3>
&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>&lt;strong>成功捕捉&lt;/strong>&lt;/td>
 &lt;td>任務完成時&lt;/td>
 &lt;td>問「有意外的成功值得記錄？」&lt;/td>
 &lt;td>完成檢查點整合（Checkpoint）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>學習捕捉&lt;/strong>&lt;/td>
 &lt;td>Session 結束時&lt;/td>
 &lt;td>問「學到什麼可推廣的做法？」&lt;/td>
 &lt;td>學習捕捉 skill&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>發現捕捉&lt;/strong>&lt;/td>
 &lt;td>開發途中發現新模式&lt;/td>
 &lt;td>記錄到錯誤模式知識庫或 insights&lt;/td>
 &lt;td>主動紀錄 + 提醒機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="自動駕駛失敗模式">自動駕駛失敗模式&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;th>對應絆腳索&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>忽略機會&lt;/td>
 &lt;td>一直說要做但沒進展&lt;/td>
 &lt;td>核心任務被救火排擠&lt;/td>
 &lt;td>偏離型&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>否認現實&lt;/td>
 &lt;td>堅持失敗的計畫&lt;/td>
 &lt;td>連續多輪失敗但繼續同方向&lt;/td>
 &lt;td>失敗型&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="切割機制">切割機制&lt;/h2>
&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>一個大任務做到哪算哪&lt;/td>
 &lt;td>拆成原子任務單位，每個有明確範圍&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>停止時機模糊&lt;/td>
 &lt;td>每個任務完成時強制檢查點（Checkpoint）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>認知負擔持續累積&lt;/td>
 &lt;td>每個任務控制認知負擔 &amp;lt;= 10&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>方向偏離沒人發現&lt;/td>
 &lt;td>檢查點（Checkpoint）時重新審查和評估&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="wrap-階段間的切割點">WRAP 階段間的切割點&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>W 完成後&lt;/td>
 &lt;td>「選項品質夠嗎？多元性足嗎？」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>R 完成後&lt;/td>
 &lt;td>「證據支持哪個選項？最強版本論證通過嗎？」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>A 完成後&lt;/td>
 &lt;td>「這符合核心優先事項嗎？機會成本可接受嗎？」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>P 完成後&lt;/td>
 &lt;td>「行前預想的風險可接受嗎？回退計畫就位了嗎？」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每個切割點都是「是否繼續」的有意識決定。&lt;/p>
&lt;hr>
&lt;h2 id="絆腳索的哲學">絆腳索的哲學&lt;/h2>
&lt;h3 id="命名效應">命名效應&lt;/h3>
&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>只看眼前選項&lt;/td>
 &lt;td>聚光燈效應&lt;/td>
 &lt;td>只想到修改本地元件，沒想到搜尋社群&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>只找支持證據&lt;/td>
 &lt;td>確認偏誤&lt;/td>
 &lt;td>假設根因是 X，只找支持 X 的證據&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>「已經改了兩輪了，再試一次」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="核心提醒">核心提醒&lt;/h3>
&lt;p>Skill 不需要每次都跑完整 WRAP 流程。&lt;/p>
&lt;p>有時只需要一句：&lt;strong>「注意，你可能在自動駕駛。你是有選擇的。」&lt;/strong>&lt;/p>
&lt;hr>
&lt;h2 id="與專案系統整合">與專案系統整合&lt;/h2>
&lt;p>絆腳索是通用概念，各專案會有各自的實作方式：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>期限型&lt;/strong> — 計時器 / 掛鉤（Hook）監測 / 決策者自律&lt;/li>
&lt;li>&lt;strong>失敗型&lt;/strong> — CI 失敗計數 / 重複 dispatch 偵測 / Bug tracker 標記&lt;/li>
&lt;li>&lt;strong>偏離型&lt;/strong> — 任務追蹤系統的 label / milestone 統計&lt;/li>
&lt;li>&lt;strong>切割&lt;/strong> — Atomic work unit / 階段性 checkpoint 機制&lt;/li>
&lt;li>&lt;strong>正面捕捉&lt;/strong> — 學習捕捉 / retrospective / 知識庫記錄&lt;/li>
&lt;/ul>
&lt;p>各專案的具體整合（掛鉤（Hook）訊號對應、CLI 提醒訊息、狀態追蹤、自動觸發機制、規則系統對應）應放在該專案自己的落地層文件。&lt;/p></description><content:encoded><![CDATA[<p>WRAP 框架的持續監控機制。絆腳索不告訴你該怎麼做，而是提醒你「你是有選擇的」。</p>
<blockquote>
<p>來源：《零偏見決斷法》— 自動駕駛模式讓人忽略機會、堅持註定失敗的計畫。</p></blockquote>
<hr>
<h2 id="絆腳索總覽">絆腳索總覽</h2>
<h3 id="防護型防止壞事">防護型（防止壞事）</h3>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>觸發條件</th>
          <th>動作</th>
          <th>實作方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>期限型</strong></td>
          <td>非核心問題花 &gt; 15 分鐘</td>
          <td>建任務延後，回到核心</td>
          <td>自律 + 計時機制</td>
      </tr>
      <tr>
          <td><strong>失敗型</strong></td>
          <td>同一修改連續 2 次失敗</td>
          <td>搜尋社群或換方向</td>
          <td>失敗計數偵測</td>
      </tr>
      <tr>
          <td><strong>偏離型</strong></td>
          <td>連續 2+ 個任務單位不在當前迭代目標</td>
          <td>回到核心任務</td>
          <td>任務追蹤系統統計</td>
      </tr>
      <tr>
          <td><strong>回退型</strong></td>
          <td>已回退過一次修改</td>
          <td>完全停止，換方向</td>
          <td>自律</td>
      </tr>
      <tr>
          <td><strong>嘗試型</strong></td>
          <td>同一問題嘗試 2 次修改</td>
          <td>向外求解（社群/AI）</td>
          <td>自律</td>
      </tr>
      <tr>
          <td><strong>決策路徑型</strong>（決策判斷干擾子型）</td>
          <td>決策流程中跳過自檢的自動駕駛（autopilot）；4 個子型：CLI 自動駕駛（autopilot） / 既有結論錨定（Anchor） / 草率改規則 / 多步驟成功率盲點</td>
          <td>依子型對應動作（見下方詳表）</td>
          <td>自律 + 掛鉤（Hook）偵測（部分）</td>
      </tr>
  </tbody>
</table>
<h3 id="決策路徑型子型詳表">決策路徑型子型詳表</h3>
<table>
  <thead>
      <tr>
          <th>子型</th>
          <th>觸發訊號</th>
          <th>絆腳索動作</th>
          <th>替代行為</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>3.1 CLI / 規則自動駕駛（autopilot）</strong></td>
          <td>CLI 撞錯後立即重試或猜變體</td>
          <td>強制「撞錯即 <code>--help</code>」規則</td>
          <td>查文件後再試</td>
      </tr>
      <tr>
          <td><strong>3.2 既有結論錨定（Anchor）</strong></td>
          <td>WRAP W 階段選項能一句話概括 / 全指向同一根因</td>
          <td>強制反向思考（Consider the Opposite）</td>
          <td>重新定義問題後 W 重跑</td>
      </tr>
      <tr>
          <td><strong>3.3 規則失敗草率改規則</strong></td>
          <td>失敗第一反應「改規則」</td>
          <td>先重試 2 次才改</td>
          <td>挖根因再決定改規則或改行為</td>
      </tr>
      <tr>
          <td><strong>3.4 多步驟成功率盲點</strong></td>
          <td>多步驟計畫中所有中間步驟都預測成功</td>
          <td>WRAP R 階段基本率檢查</td>
          <td>每步獨立驗證</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>本表提供 WRAP 觸發時的查表入口；各專案可把這些子型映射到自己的掛鉤（Hook）或任務系統。</p></blockquote>
<h3 id="正面型捕捉好事">正面型（捕捉好事）</h3>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>觸發條件</th>
          <th>動作</th>
          <th>實作方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>成功捕捉</strong></td>
          <td>任務完成時</td>
          <td>問「有意外的成功值得記錄？」</td>
          <td>完成檢查點整合（Checkpoint）</td>
      </tr>
      <tr>
          <td><strong>學習捕捉</strong></td>
          <td>Session 結束時</td>
          <td>問「學到什麼可推廣的做法？」</td>
          <td>學習捕捉 skill</td>
      </tr>
      <tr>
          <td><strong>發現捕捉</strong></td>
          <td>開發途中發現新模式</td>
          <td>記錄到錯誤模式知識庫或 insights</td>
          <td>主動紀錄 + 提醒機制</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="自動駕駛失敗模式">自動駕駛失敗模式</h2>
<p>決策者進入自動駕駛的兩種表現：</p>
<table>
  <thead>
      <tr>
          <th>失敗類型</th>
          <th>表現</th>
          <th>範例</th>
          <th>對應絆腳索</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>忽略機會</td>
          <td>一直說要做但沒進展</td>
          <td>核心任務被救火排擠</td>
          <td>偏離型</td>
      </tr>
      <tr>
          <td>否認現實</td>
          <td>堅持失敗的計畫</td>
          <td>連續多輪失敗但繼續同方向</td>
          <td>失敗型</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="切割機制">切割機制</h2>
<p>切割把連續的資源變成不連續的區塊，每個區塊結束時強迫問「是否繼續？」</p>
<table>
  <thead>
      <tr>
          <th>無切割（碗裝洋芋片）</th>
          <th>有切割（小包裝）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>一個大任務做到哪算哪</td>
          <td>拆成原子任務單位，每個有明確範圍</td>
      </tr>
      <tr>
          <td>停止時機模糊</td>
          <td>每個任務完成時強制檢查點（Checkpoint）</td>
      </tr>
      <tr>
          <td>認知負擔持續累積</td>
          <td>每個任務控制認知負擔 &lt;= 10</td>
      </tr>
      <tr>
          <td>方向偏離沒人發現</td>
          <td>檢查點（Checkpoint）時重新審查和評估</td>
      </tr>
  </tbody>
</table>
<h3 id="wrap-階段間的切割點">WRAP 階段間的切割點</h3>
<table>
  <thead>
      <tr>
          <th>切割點</th>
          <th>問題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>W 完成後</td>
          <td>「選項品質夠嗎？多元性足嗎？」</td>
      </tr>
      <tr>
          <td>R 完成後</td>
          <td>「證據支持哪個選項？最強版本論證通過嗎？」</td>
      </tr>
      <tr>
          <td>A 完成後</td>
          <td>「這符合核心優先事項嗎？機會成本可接受嗎？」</td>
      </tr>
      <tr>
          <td>P 完成後</td>
          <td>「行前預想的風險可接受嗎？回退計畫就位了嗎？」</td>
      </tr>
  </tbody>
</table>
<p>每個切割點都是「是否繼續」的有意識決定。</p>
<hr>
<h2 id="絆腳索的哲學">絆腳索的哲學</h2>
<h3 id="命名效應">命名效應</h3>
<p>給認知偏誤命名，就更容易注意到它：</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>假設根因是 X，只找支持 X 的證據</td>
      </tr>
      <tr>
          <td>覺得自己比別人厲害</td>
          <td>過度自信</td>
          <td>「其他人遇到的和我的不一樣」</td>
      </tr>
      <tr>
          <td>花了時間就想繼續</td>
          <td>沈沒成本</td>
          <td>「已經改了兩輪了，再試一次」</td>
      </tr>
  </tbody>
</table>
<h3 id="核心提醒">核心提醒</h3>
<p>Skill 不需要每次都跑完整 WRAP 流程。</p>
<p>有時只需要一句：<strong>「注意，你可能在自動駕駛。你是有選擇的。」</strong></p>
<hr>
<h2 id="與專案系統整合">與專案系統整合</h2>
<p>絆腳索是通用概念，各專案會有各自的實作方式：</p>
<ul>
<li><strong>期限型</strong> — 計時器 / 掛鉤（Hook）監測 / 決策者自律</li>
<li><strong>失敗型</strong> — CI 失敗計數 / 重複 dispatch 偵測 / Bug tracker 標記</li>
<li><strong>偏離型</strong> — 任務追蹤系統的 label / milestone 統計</li>
<li><strong>切割</strong> — Atomic work unit / 階段性 checkpoint 機制</li>
<li><strong>正面捕捉</strong> — 學習捕捉 / retrospective / 知識庫記錄</li>
</ul>
<p>各專案的具體整合（掛鉤（Hook）訊號對應、CLI 提醒訊息、狀態追蹤、自動觸發機制、規則系統對應）應放在該專案自己的落地層文件。</p>
<hr>
<p><strong>Last Updated</strong>: 2026-04-28
<strong>Version</strong>: 2.1.0 — 防護型新增「決策路徑型」分類，含 4 子型詳表（CLI 自動駕駛（autopilot） / 既有結論錨定（Anchor） / 草率改規則 / 多步驟成功率盲點）。
<strong>Version</strong>: 2.0.0</p>
]]></content:encoded></item><item><title>Accessibility and Focus — A11y 三道防線</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/accessibility-and-focus/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/accessibility-and-focus/</guid><description>&lt;p>A11y 三道防線：靜態（鍵盤可達性三要素）、動態（focus 跟 aria-live）、優先 Native HTML &amp;gt; ARIA。鍵盤 / 視覺 / motor / 認知都納入。&lt;/p>
&lt;p>適用：寫互動 UI、JS reparent / hide 元素、自製 component（modal / dropdown / tabs）、客製外部組件後檢查 a11y。
不適用：純後端 / 純資料流（沒有使用者直接互動）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋鍵盤可達性三要素、focus management 模板、aria-live 設計、native HTML 優先原則。&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>自製 modal / dropdown / tabs / accordion&lt;/td>
 &lt;td>先看有沒有 &lt;code>&amp;lt;dialog&amp;gt;&lt;/code> / &lt;code>&amp;lt;details&amp;gt;&lt;/code> 能用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>JS reparent 或 hide 元素&lt;/td>
 &lt;td>保存 focus、操作後還原&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>動態變動內容（搜尋結果、filter 切換、status 訊息）&lt;/td>
 &lt;td>加 &lt;code>aria-live&lt;/code> region&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>使用者反映「鍵盤跑掉」「Tab 順序怪」&lt;/td>
 &lt;td>檢查 visible focus indicator + tab order&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將寫 &lt;code>role=&amp;quot;button&amp;quot;&lt;/code> &lt;code>role=&amp;quot;dialog&amp;quot;&lt;/code> 等 ARIA role&lt;/td>
 &lt;td>停 — 看 native HTML 能不能用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>行動裝置誤點&lt;/td>
 &lt;td>檢查 hit target 大小（最小 44×44 px）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼-a11y-是預設不是補丁">為什麼 a11y 是預設不是補丁&lt;/h2>
&lt;p>A11y 不是「完整功能後再加上」、是&lt;strong>設計時就決定的結構&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>用 &lt;code>&amp;lt;button&amp;gt;&lt;/code> vs &lt;code>&amp;lt;div onclick&amp;gt;&lt;/code> → 鍵盤 / focus / a11y tree 自帶 vs 全部要自己補&lt;/li>
&lt;li>modal 用 &lt;code>&amp;lt;dialog&amp;gt;&lt;/code> vs 自己組 → focus trap / escape / scrollable / inert 自帶 vs 全部要自己補&lt;/li>
&lt;li>動態內容變動有 aria-live vs 沒 → screen reader 知道 vs 不知道&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>事後補 a11y 比事前設計貴 5-10 倍&lt;/strong>。寫之前先選對結構、後續成本低。&lt;/p>
&lt;hr>
&lt;h2 id="防線-1靜態鍵盤可達性三要素">防線 1：靜態鍵盤可達性三要素&lt;/h2>
&lt;p>鍵盤使用者要能用、三個元素缺一不可：&lt;/p>
&lt;h3 id="要素-1visible-focus-indicator">要素 1：Visible focus indicator&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c">/* 反例：去掉預設 focus outline */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nt">button&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nd">focus&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">outline&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">none&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">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c">/* 對例：可見的 focus indicator */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="nt">button&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nd">focus-visible&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="k">outline&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="kc">solid&lt;/span> &lt;span class="nf">var&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="n">focus&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="kc">color&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="k">outline-offset&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="kt">px&lt;/span>&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="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>:focus-visible&lt;/code>（鍵盤 focus）跟 &lt;code>:focus&lt;/code>（含滑鼠 click 後）區分 — 滑鼠使用者不需要看到 outline、鍵盤使用者必須看到。&lt;/p>
&lt;h3 id="要素-2邏輯-tab-順序">要素 2：邏輯 Tab 順序&lt;/h3>
&lt;p>Tab 順序預設由 DOM tree 決定。如果視覺順序跟 DOM 順序不同（例如用 CSS grid 重排），考慮：&lt;/p>
&lt;ul>
&lt;li>重排 DOM 順序對齊視覺&lt;/li>
&lt;li>用 &lt;code>tabindex=&amp;quot;0&amp;quot;&lt;/code> 讓元素可 focus（不要用 &amp;gt; 0）&lt;/li>
&lt;li>不要用 &lt;code>tabindex=&amp;quot;-1&amp;quot;&lt;/code> 跳過該 focus 的元素&lt;/li>
&lt;/ul>
&lt;h3 id="要素-3modal--drawer-有-escape-路徑">要素 3：Modal / drawer 有 escape 路徑&lt;/h3>





&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="nx">dialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;keydown&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">key&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;Escape&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nx">dialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">close&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="p">});&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>或用 &lt;code>&amp;lt;dialog&amp;gt;&lt;/code> native — &lt;code>Escape&lt;/code> 自帶。&lt;/p></description><content:encoded><![CDATA[<p>A11y 三道防線：靜態（鍵盤可達性三要素）、動態（focus 跟 aria-live）、優先 Native HTML &gt; ARIA。鍵盤 / 視覺 / motor / 認知都納入。</p>
<p>適用：寫互動 UI、JS reparent / hide 元素、自製 component（modal / dropdown / tabs）、客製外部組件後檢查 a11y。
不適用：純後端 / 純資料流（沒有使用者直接互動）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋鍵盤可達性三要素、focus management 模板、aria-live 設計、native HTML 優先原則。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>自製 modal / dropdown / tabs / accordion</td>
          <td>先看有沒有 <code>&lt;dialog&gt;</code> / <code>&lt;details&gt;</code> 能用</td>
      </tr>
      <tr>
          <td>JS reparent 或 hide 元素</td>
          <td>保存 focus、操作後還原</td>
      </tr>
      <tr>
          <td>動態變動內容（搜尋結果、filter 切換、status 訊息）</td>
          <td>加 <code>aria-live</code> region</td>
      </tr>
      <tr>
          <td>使用者反映「鍵盤跑掉」「Tab 順序怪」</td>
          <td>檢查 visible focus indicator + tab order</td>
      </tr>
      <tr>
          <td>即將寫 <code>role=&quot;button&quot;</code> <code>role=&quot;dialog&quot;</code> 等 ARIA role</td>
          <td>停 — 看 native HTML 能不能用</td>
      </tr>
      <tr>
          <td>行動裝置誤點</td>
          <td>檢查 hit target 大小（最小 44×44 px）</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼-a11y-是預設不是補丁">為什麼 a11y 是預設不是補丁</h2>
<p>A11y 不是「完整功能後再加上」、是<strong>設計時就決定的結構</strong>：</p>
<ul>
<li>用 <code>&lt;button&gt;</code> vs <code>&lt;div onclick&gt;</code> → 鍵盤 / focus / a11y tree 自帶 vs 全部要自己補</li>
<li>modal 用 <code>&lt;dialog&gt;</code> vs 自己組 → focus trap / escape / scrollable / inert 自帶 vs 全部要自己補</li>
<li>動態內容變動有 aria-live vs 沒 → screen reader 知道 vs 不知道</li>
</ul>
<p><strong>事後補 a11y 比事前設計貴 5-10 倍</strong>。寫之前先選對結構、後續成本低。</p>
<hr>
<h2 id="防線-1靜態鍵盤可達性三要素">防線 1：靜態鍵盤可達性三要素</h2>
<p>鍵盤使用者要能用、三個元素缺一不可：</p>
<h3 id="要素-1visible-focus-indicator">要素 1：Visible focus indicator</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">/* 反例：去掉預設 focus outline */</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nt">button</span><span class="p">:</span><span class="nd">focus</span> <span class="p">{</span> <span class="k">outline</span><span class="p">:</span> <span class="kc">none</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">/* 對例：可見的 focus indicator */</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nt">button</span><span class="p">:</span><span class="nd">focus-visible</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="k">outline</span><span class="p">:</span> <span class="mi">2</span><span class="kt">px</span> <span class="kc">solid</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">focus</span><span class="o">-</span><span class="kc">color</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="k">outline-offset</span><span class="p">:</span> <span class="mi">2</span><span class="kt">px</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><code>:focus-visible</code>（鍵盤 focus）跟 <code>:focus</code>（含滑鼠 click 後）區分 — 滑鼠使用者不需要看到 outline、鍵盤使用者必須看到。</p>
<h3 id="要素-2邏輯-tab-順序">要素 2：邏輯 Tab 順序</h3>
<p>Tab 順序預設由 DOM tree 決定。如果視覺順序跟 DOM 順序不同（例如用 CSS grid 重排），考慮：</p>
<ul>
<li>重排 DOM 順序對齊視覺</li>
<li>用 <code>tabindex=&quot;0&quot;</code> 讓元素可 focus（不要用 &gt; 0）</li>
<li>不要用 <code>tabindex=&quot;-1&quot;</code> 跳過該 focus 的元素</li>
</ul>
<h3 id="要素-3modal--drawer-有-escape-路徑">要素 3：Modal / drawer 有 escape 路徑</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="nx">dialog</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;keydown&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">e</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="k">if</span> <span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="s1">&#39;Escape&#39;</span><span class="p">)</span> <span class="nx">dialog</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>或用 <code>&lt;dialog&gt;</code> native — <code>Escape</code> 自帶。</p>
<hr>
<h2 id="防線-2動態-a11y">防線 2：動態 a11y</h2>
<h3 id="focus-management-on-dom-move">Focus management on DOM move</h3>
<p>JS reparent / hide 元素時、focus 會跑掉（落到 body）。需要保存與還原：</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="kd">function</span> <span class="nx">moveFilter</span><span class="p">(</span><span class="nx">targetSlot</span><span class="p">)</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">filter</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;.filter&#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">focused</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">activeElement</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">wasFilterFocused</span> <span class="o">=</span> <span class="nx">filter</span><span class="p">.</span><span class="nx">contains</span><span class="p">(</span><span class="nx">focused</span><span class="p">);</span>
</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 class="nx">targetSlot</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>  <span class="c1">// reparent
</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="k">if</span> <span class="p">(</span><span class="nx">wasFilterFocused</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nx">focused</span><span class="p">.</span><span class="nx">focus</span><span class="p">();</span>  <span class="c1">// 還原 focus
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="aria-live-廣播動態變動">aria-live 廣播動態變動</h3>
<p>Screen reader 預設不會朗讀「DOM 變動」、要明確告訴它：</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;!-- polite：等使用者操作完才朗讀（搜尋結果數量、filter 切換） --&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">aria-live</span><span class="o">=</span><span class="s">&#34;polite&#34;</span> <span class="na">aria-atomic</span><span class="o">=</span><span class="s">&#34;true&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  顯示 12 筆結果
</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="p">&gt;</span>
</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 class="c">&lt;!-- assertive：立刻打斷朗讀（錯誤訊息、緊急狀態） --&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="na">aria-live</span><span class="o">=</span><span class="s">&#34;assertive&#34;</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;alert&#34;</span><span class="p">&gt;</span>
</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 class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></span></span></code></pre></div><p><code>aria-atomic=&quot;true&quot;</code> 整段重讀（不只朗讀變動的部分）。</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">class</span><span class="o">=</span><span class="s">&#34;results&#34;</span> <span class="na">aria-live</span><span class="o">=</span><span class="s">&#34;polite&#34;</span> <span class="na">aria-atomic</span><span class="o">=</span><span class="s">&#34;false&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">2</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;status&#34;</span><span class="p">&gt;</span>顯示 <span class="p">&lt;</span><span class="nt">span</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;count&#34;</span><span class="p">&gt;</span>12<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> 筆結果<span class="p">&lt;/</span><span class="nt">p</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">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">4</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>JS 更新 <code>#count</code> 的 textContent 時、screen reader 朗讀「顯示 12 筆結果」。</p>
<hr>
<h2 id="防線-3native-html--aria">防線 3：Native HTML &gt; ARIA</h2>
<h3 id="為什麼優先-native">為什麼優先 Native</h3>
<table>
  <thead>
      <tr>
          <th>元素</th>
          <th>Native 自帶</th>
          <th>ARIA 補強需要</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>&lt;button&gt;</code></td>
          <td>Tab focus、Enter/Space 觸發、a11y role、disabled 狀態</td>
          <td><code>role=&quot;button&quot;</code> + tabindex + keydown listener + aria-disabled</td>
      </tr>
      <tr>
          <td><code>&lt;dialog&gt;</code></td>
          <td>Modal focus trap、Escape 關閉、<code>::backdrop</code>、<code>inert</code> 外層</td>
          <td><code>role=&quot;dialog&quot;</code> + aria-modal + 自寫 focus trap + Escape handler + inert polyfill</td>
      </tr>
      <tr>
          <td><code>&lt;details&gt;</code></td>
          <td>Toggle 展開、鍵盤、a11y</td>
          <td><code>role=&quot;region&quot;</code> + aria-expanded + 自寫 click handler + keyboard support</td>
      </tr>
      <tr>
          <td><code>&lt;fieldset&gt;+&lt;legend&gt;</code></td>
          <td>群組 a11y、screen reader 讀 legend</td>
          <td><code>role=&quot;radiogroup&quot;</code> + aria-labelledby</td>
      </tr>
      <tr>
          <td><code>&lt;input type=&quot;...&quot;&gt;</code></td>
          <td>各種 input 的 native UX、validation、a11y</td>
          <td>全部自寫</td>
      </tr>
  </tbody>
</table>
<h3 id="何時用-aria">何時用 ARIA</h3>
<p>ARIA 是補強、不是替代：</p>
<ul>
<li>用 native 但 a11y tree 還不夠（標 aria-label / aria-describedby 補語意）</li>
<li>真的沒有 native 元素（complex composite widget、tabs、tree）</li>
<li>動態變動需要廣播（aria-live）</li>
</ul>
<h3 id="範例自製-toggle-還是-native-checkbox">範例：自製 toggle 還是 native checkbox</h3>
<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="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;toggle&#34;</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;switch&#34;</span> <span class="na">tabindex</span><span class="o">=</span><span class="s">&#34;0&#34;</span> <span class="na">aria-checked</span><span class="o">=</span><span class="s">&#34;false&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;track&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">span</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="p">&gt;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nx">toggle</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</span> <span class="p">...);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nx">toggle</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;keydown&#39;</span><span class="p">,</span> <span class="nx">e</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="s1">&#39;Enter&#39;</span> <span class="o">||</span> <span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="s1">&#39; &#39;</span><span class="p">)</span> <span class="p">...;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div><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="p">&lt;</span><span class="nt">label</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;toggle&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;checkbox&#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">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;track&#34;</span> <span class="na">aria-hidden</span><span class="o">=</span><span class="s">&#34;true&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">span</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">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;visually-hidden&#34;</span><span class="p">&gt;</span>啟用 dark mode<span class="p">&lt;/</span><span class="nt">span</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">label</span><span class="p">&gt;</span></span></span></code></pre></div>




<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">toggle</span> <span class="nt">input</span> <span class="p">{</span> <span class="k">position</span><span class="p">:</span> <span class="kc">absolute</span><span class="p">;</span> <span class="k">opacity</span><span class="p">:</span> <span class="mi">0</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">toggle</span> <span class="nt">input</span><span class="p">:</span><span class="nd">checked</span> <span class="o">+</span> <span class="p">.</span><span class="nc">track</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="p">}</span></span></span></code></pre></div><p>Native checkbox 自帶 keyboard / focus / state、CSS 把它隱藏、視覺用 <code>.track</code> 呈現。</p>
<hr>
<h2 id="視覺--motor-a11y">視覺 / Motor a11y</h2>
<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">/* 對比度 */</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">:</span><span class="nd">root</span> <span class="p">{</span> <span class="nv">--text</span><span class="p">:</span> <span class="mh">#1a202c</span><span class="p">;</span> <span class="nv">--bg</span><span class="p">:</span> <span class="mh">#fff</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">/* WCAG AA: 普通文字 4.5:1、大文字 3:1 */</span>
</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"><span class="c">/* 字型放大時不破版 */</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">.</span><span class="nc">container</span> <span class="p">{</span> <span class="k">max-width</span><span class="p">:</span> <span class="mi">60</span><span class="kt">ch</span><span class="p">;</span> <span class="p">}</span>  <span class="c">/* ch 跟字型同步 */</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">.</span><span class="nc">text</span> <span class="p">{</span> <span class="k">font-size</span><span class="p">:</span> <span class="mi">1</span><span class="kt">rem</span><span class="p">;</span> <span class="k">line-height</span><span class="p">:</span> <span class="mf">1.6</span><span class="p">;</span> <span class="p">}</span>  <span class="c">/* rem 跟使用者設定同步 */</span>
</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 class="c">/* prefers-reduced-motion */</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">@</span><span class="k">media</span> <span class="o">(</span><span class="nt">prefers-reduced-motion</span><span class="o">:</span> <span class="nt">reduce</span><span class="o">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="o">*</span> <span class="p">{</span> <span class="k">animation-duration</span><span class="p">:</span> <span class="mf">0.01</span><span class="kt">ms</span> <span class="cp">!important</span><span class="p">;</span> <span class="k">transition-duration</span><span class="p">:</span> <span class="mf">0.01</span><span class="kt">ms</span> <span class="cp">!important</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></span></code></pre></div><h3 id="motor--hit-target">Motor / Hit target</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">/* 觸控 hit target 最小 44×44 px (WCAG AAA) */</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nt">button</span><span class="o">,</span> <span class="nt">a</span><span class="o">,</span> <span class="o">[</span><span class="nt">role</span><span class="o">=</span><span class="s2">&#34;button&#34;</span><span class="o">]</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">min-height</span><span class="p">:</span> <span class="mi">44</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="k">min-width</span><span class="p">:</span> <span class="mi">44</span><span class="kt">px</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></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c">/* 兩個 hit target 之間留 8px+ 間距、避免誤點 */</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">.</span><span class="nc">toolbar</span> <span class="o">&gt;</span> <span class="o">*</span> <span class="o">+</span> <span class="o">*</span> <span class="p">{</span> <span class="k">margin-left</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></code></pre></div><hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1自製-dropdown">範例 1：自製 dropdown</h3>
<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="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;dropdown&#34;</span> <span class="na">tabindex</span><span class="o">=</span><span class="s">&#34;0&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span>選單<span class="p">&lt;/</span><span class="nt">span</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">class</span><span class="o">=</span><span class="s">&#34;menu&#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">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;item&#34;</span><span class="p">&gt;</span>選項 1<span class="p">&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">class</span><span class="o">=</span><span class="s">&#34;item&#34;</span><span class="p">&gt;</span>選項 2<span class="p">&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="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>問題：no native focus、no keyboard、no a11y role、screen reader 不知道是 menu。</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="p">&lt;</span><span class="nt">button</span> <span class="na">aria-haspopup</span><span class="o">=</span><span class="s">&#34;menu&#34;</span> <span class="na">aria-expanded</span><span class="o">=</span><span class="s">&#34;false&#34;</span> <span class="na">aria-controls</span><span class="o">=</span><span class="s">&#34;menu1&#34;</span><span class="p">&gt;</span>
</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 class="p">&lt;/</span><span class="nt">button</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">ul</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;menu1&#34;</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;menu&#34;</span> <span class="na">hidden</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">li</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;menuitem&#34;</span><span class="p">&gt;&lt;</span><span class="nt">button</span><span class="p">&gt;</span>選項 1<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;&lt;/</span><span class="nt">li</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">li</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;menuitem&#34;</span><span class="p">&gt;&lt;</span><span class="nt">button</span><span class="p">&gt;</span>選項 2<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;&lt;/</span><span class="nt">li</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">ul</span><span class="p">&gt;</span></span></span></code></pre></div><p>或如果是「選擇一個」 → <code>&lt;select&gt;</code> native。</p>
<h3 id="範例-2filter-切換沒-a11y-broadcast">範例 2：filter 切換沒 a11y broadcast</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="nx">button</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</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="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 class="nx">forEach</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="nx">r</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">tag</span> <span class="o">===</span> <span class="nx">currentFilter</span> <span class="o">?</span> <span class="s1">&#39;block&#39;</span> <span class="o">:</span> <span class="s1">&#39;none&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <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">// screen reader 不知道結果變了
</span></span></span></code></pre></div><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="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;results&#34;</span> <span class="na">aria-live</span><span class="o">=</span><span class="s">&#34;polite&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">2</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;status&#34;</span><span class="p">&gt;</span>顯示 <span class="p">&lt;</span><span class="nt">span</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;count&#34;</span><span class="p">&gt;</span>12<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> 筆結果（filter: <span class="p">&lt;</span><span class="nt">span</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;filter&#34;</span><span class="p">&gt;</span>全部<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>）<span class="p">&lt;/</span><span class="nt">p</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="p">&gt;</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="nx">button</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</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="c1">// ... filter logic
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;count&#39;</span><span class="p">).</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">visibleCount</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;filter&#39;</span><span class="p">).</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">currentFilter</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="c1">// aria-live 自動朗讀
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="p">});</span></span></span></code></pre></div><h3 id="範例-3js-移動元素-focus-跑掉">範例 3：JS 移動元素 focus 跑掉</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">// resize 時把 filter 從 mobile drawer 移到 desktop sidebar
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="nx">mediaQuery</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;change&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">mediaQuery</span><span class="p">.</span><span class="nx">matches</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">sidebar</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nx">drawer</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</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><span class="line"><span class="ln">9</span><span class="cl"><span class="c1">// 如果 filter 內的某個 input 有 focus、reparent 後 focus 落到 body
</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="nx">mediaQuery</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;change&#39;</span><span class="p">,</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">focused</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">activeElement</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">wasInFilter</span> <span class="o">=</span> <span class="nx">filter</span><span class="p">.</span><span class="nx">contains</span><span class="p">(</span><span class="nx">focused</span><span class="p">);</span>
</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">  <span class="k">if</span> <span class="p">(</span><span class="nx">mediaQuery</span><span class="p">.</span><span class="nx">matches</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nx">sidebar</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nx">drawer</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="p">}</span>
</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 class="k">if</span> <span class="p">(</span><span class="nx">wasInFilter</span><span class="p">)</span> <span class="nx">focused</span><span class="p">.</span><span class="nx">focus</span><span class="p">();</span>  <span class="c1">// 還原 focus
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="p">});</span></span></span></code></pre></div><hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>寫互動 UI 時：</p>
<ul>
<li><input disabled="" type="checkbox"> 用 <code>&lt;button&gt;</code> <code>&lt;dialog&gt;</code> <code>&lt;details&gt;</code> <code>&lt;fieldset&gt;</code> 取代自製 ARIA 結構？</li>
<li><input disabled="" type="checkbox"> visible focus indicator 沒被 <code>outline: none</code> 拿掉？</li>
<li><input disabled="" type="checkbox"> Tab 順序符合視覺順序（沒用 <code>tabindex &gt; 0</code>）？</li>
<li><input disabled="" type="checkbox"> Modal / drawer 有 Escape 關閉路徑？</li>
<li><input disabled="" type="checkbox"> JS reparent / hide 時保存與還原 focus？</li>
<li><input disabled="" type="checkbox"> 動態變動內容用 <code>aria-live</code> 廣播？</li>
<li><input disabled="" type="checkbox"> 對比度 ≥ 4.5:1（普通文字）？</li>
<li><input disabled="" type="checkbox"> Hit target ≥ 44×44 px？</li>
<li><input disabled="" type="checkbox"> <code>prefers-reduced-motion</code> 時關掉動畫？</li>
</ul>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code>）：</p>
<ul>
<li><a href="/blog/report/focus-management-on-dom-move/" data-link-title="動態 DOM 移動時的 focus 管理" data-link-desc="Filter slot 跨 viewport 搬節點、scope filter 隱藏結果 — 這類 DOM 變動會讓鍵盤 focus 跑掉或停在不可見位置。本文盤點動態 DOM 對 focus 的影響與檢查方法。">focus-management-on-dom-move</a> — 動態 DOM 移動時的 focus 管理</li>
<li><a href="/blog/report/aria-live-for-dynamic-content/" data-link-title="Screen reader 與動態內容變動的 live region 設計" data-link-desc="Scope filter 切換、結果數量變動 — screen reader 使用者看不到視覺變動、需要 aria-live region 主動朗讀。本文盤點 live region 的設計選擇與適用情境。">aria-live-for-dynamic-content</a> — Screen reader 與動態內容變動的 live region 設計</li>
<li><a href="/blog/report/native-html-over-aria-role/" data-link-title="Native HTML element 優先於 ARIA role 的取捨" data-link-desc="用 `&lt;fieldset&gt;&lt;legend&gt;` 比 `&lt;div role=&#34;radiogroup&#34;&gt;` 安全、用 `&lt;button&gt;` 比 `&lt;div role=&#34;button&#34;&gt;` 直接 — native element 自帶完整無障礙語意與行為。本文盤點 ARIA role 是 fallback、不是 default。">native-html-over-aria-role</a> — Native HTML element 優先於 ARIA role 的取捨</li>
<li><a href="/blog/report/keyboard-accessibility/" data-link-title="鍵盤可達性：focus indicator、tab 順序、escape 路徑" data-link-desc="鍵盤使用者用 tab / shift&#43;tab 導航、enter / space 激活、esc 退出。三件事決定可不可用：focus 是否可見、tab 順序是否合理、modal / overlay 有沒有 escape 路徑。本文盤點搜尋頁的鍵盤 a11y 風險點。">keyboard-accessibility</a> — 鍵盤可達性：focus indicator、tab 順序、escape 路徑</li>
<li><a href="/blog/report/motor-accessibility-hit-target/" data-link-title="Motor 可達性：hit target、間距、誤點防護" data-link-desc="Hit target 太小會讓行動裝置使用者誤點、motor 障礙使用者更甚。WCAG AAA 建議 ≥ 44×44px、間距足夠避免誤觸。本文展開 hit target 設計與相關 motor a11y 風險點。">motor-accessibility-hit-target</a> — Motor 可達性：hit target、間距、誤點防護</li>
<li><a href="/blog/report/visual-aids-contrast-zoom-responsive/" data-link-title="視覺輔助：對比度、放大、字型 zoom 的 layout 適配" data-link-desc="色弱、低對比敏感、低視力使用者跟一般使用者「看到的不是同一個 UI」 — 對比度足夠嗎、絕對定位元件在放大模式下是否可達、字型放大 200% 後 layout 還好嗎。本文盤點視覺呈現面的 a11y 風險點。">visual-aids-contrast-zoom-responsive</a> — 視覺輔助：對比度、放大、字型 zoom 的 layout 適配</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.1.0</p>
]]></content:encoded></item><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>CSS / JS Boundary — CSS / JS 邊界與 specificity 處理</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/css-js-boundary/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/css-js-boundary/</guid><description>&lt;p>CSS 跟 JS 各自負責什麼、邊界由「值能不能 build-time 定下來」決定。&lt;code>!important&lt;/code> / inline style / specificity 戰是訊號、不是工具。&lt;/p>
&lt;p>適用：寫 / 改 CSS 規則、決定 styling 該放 CSS 還是 JS、跟 vendor CSS 共存、檔案組織。
不適用：純 logic JS（沒涉及 styling）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋 CSS-only vs JS-assisted 判準、class toggle 模式、CSS layers、variable 單一位置、檔案拆分。&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>不確定值該寫進 CSS 還是 JS&lt;/td>
 &lt;td>問「能 build-time 定下來嗎」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將寫 &lt;code>!important&lt;/code>&lt;/td>
 &lt;td>停 — 換 CSS layers 思路&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將寫 &lt;code>el.style.setProperty(..., 'important')&lt;/code>&lt;/td>
 &lt;td>停 — 換 class toggle&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Inline &lt;code>&amp;lt;style&amp;gt;&lt;/code> / &lt;code>&amp;lt;script&amp;gt;&lt;/code> 超過 30 行&lt;/td>
 &lt;td>拆出獨立檔案、讓 Hugo / build pipeline 處理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CSS variable 在 3 個地方定義&lt;/td>
 &lt;td>集中到單一定義位置、其他地方只引用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Vendor CSS 跟自家 CSS 打 specificity 戰&lt;/td>
 &lt;td>&lt;code>@layer&lt;/code> 包 vendor、自家 unlayered 自動贏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Runtime 量測值跟 hardcoded 值在同一個對齊基準上混用&lt;/td>
 &lt;td>全選一邊、不要混搭&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼-css--js-邊界要清楚">為什麼 CSS / JS 邊界要清楚&lt;/h2>
&lt;p>樣式邏輯散落在 inline style + CSS file + JS setProperty + &lt;code>!important&lt;/code> 的後果：&lt;/p>
&lt;ol>
&lt;li>改一個顏色要 grep 三個地方、其中一個改不到&lt;/li>
&lt;li>DevTools 看不出「為什麼這個值在這裡」（inline style 沒 class hint、important 是核武）&lt;/li>
&lt;li>升級 vendor 後 specificity 戰失敗、自家規則失效&lt;/li>
&lt;/ol>
&lt;p>清楚的邊界 = &lt;strong>CSS 描述「在某狀態下長什麼樣」、JS 切換狀態（toggle class / 寫 var）&lt;/strong>。樣式定義集中在 CSS、JS 不直接操作 inline style。&lt;/p>
&lt;hr>
&lt;h2 id="邊界判準值能不能-build-time-定下來">邊界判準：值能不能 build-time 定下來&lt;/h2>
&lt;h3 id="css-only值能-build-time-定下來">CSS-only：值能 build-time 定下來&lt;/h3>
&lt;ul>
&lt;li>Design token（&lt;code>--brand-color&lt;/code>、&lt;code>--gap-base&lt;/code>）&lt;/li>
&lt;li>固定 breakpoint / aspect ratio&lt;/li>
&lt;li>元件預設尺寸&lt;/li>
&lt;li>跨狀態的視覺差異（&lt;code>.expanded&lt;/code>、&lt;code>.loading&lt;/code>）&lt;/li>
&lt;/ul>
&lt;p>寫成 CSS variable + class toggle、JS 只負責加減 class。&lt;/p>
&lt;h3 id="js-assisted必須-runtime-才能知道">JS-assisted：必須 runtime 才能知道&lt;/h3>
&lt;ul>
&lt;li>Form 高度（隨字型 / line-height 變動）&lt;/li>
&lt;li>Container 寬度（隨 viewport / sidebar 變動）&lt;/li>
&lt;li>Scroll position&lt;/li>
&lt;li>元素的 bounding rect&lt;/li>
&lt;/ul>
&lt;p>JS 量測後&lt;strong>寫回 CSS variable&lt;/strong>、CSS 仍然只讀變數：&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">const&lt;/span> &lt;span class="nx">formHeight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">form&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getBoundingClientRect&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">height&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="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">documentElement&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">style&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">setProperty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;--form-height&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">formHeight&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">px`&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">scope&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">top&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">calc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">var&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="n">form&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">height&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nf">var&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="n">gap&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>CSS 不知道值怎麼來的、只知道讀 var — 換 framework / 換量測方式時、CSS 不動。&lt;/p></description><content:encoded><![CDATA[<p>CSS 跟 JS 各自負責什麼、邊界由「值能不能 build-time 定下來」決定。<code>!important</code> / inline style / specificity 戰是訊號、不是工具。</p>
<p>適用：寫 / 改 CSS 規則、決定 styling 該放 CSS 還是 JS、跟 vendor CSS 共存、檔案組織。
不適用：純 logic JS（沒涉及 styling）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋 CSS-only vs JS-assisted 判準、class toggle 模式、CSS layers、variable 單一位置、檔案拆分。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>不確定值該寫進 CSS 還是 JS</td>
          <td>問「能 build-time 定下來嗎」</td>
      </tr>
      <tr>
          <td>即將寫 <code>!important</code></td>
          <td>停 — 換 CSS layers 思路</td>
      </tr>
      <tr>
          <td>即將寫 <code>el.style.setProperty(..., 'important')</code></td>
          <td>停 — 換 class toggle</td>
      </tr>
      <tr>
          <td>Inline <code>&lt;style&gt;</code> / <code>&lt;script&gt;</code> 超過 30 行</td>
          <td>拆出獨立檔案、讓 Hugo / build pipeline 處理</td>
      </tr>
      <tr>
          <td>CSS variable 在 3 個地方定義</td>
          <td>集中到單一定義位置、其他地方只引用</td>
      </tr>
      <tr>
          <td>Vendor CSS 跟自家 CSS 打 specificity 戰</td>
          <td><code>@layer</code> 包 vendor、自家 unlayered 自動贏</td>
      </tr>
      <tr>
          <td>Runtime 量測值跟 hardcoded 值在同一個對齊基準上混用</td>
          <td>全選一邊、不要混搭</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼-css--js-邊界要清楚">為什麼 CSS / JS 邊界要清楚</h2>
<p>樣式邏輯散落在 inline style + CSS file + JS setProperty + <code>!important</code> 的後果：</p>
<ol>
<li>改一個顏色要 grep 三個地方、其中一個改不到</li>
<li>DevTools 看不出「為什麼這個值在這裡」（inline style 沒 class hint、important 是核武）</li>
<li>升級 vendor 後 specificity 戰失敗、自家規則失效</li>
</ol>
<p>清楚的邊界 = <strong>CSS 描述「在某狀態下長什麼樣」、JS 切換狀態（toggle class / 寫 var）</strong>。樣式定義集中在 CSS、JS 不直接操作 inline style。</p>
<hr>
<h2 id="邊界判準值能不能-build-time-定下來">邊界判準：值能不能 build-time 定下來</h2>
<h3 id="css-only值能-build-time-定下來">CSS-only：值能 build-time 定下來</h3>
<ul>
<li>Design token（<code>--brand-color</code>、<code>--gap-base</code>）</li>
<li>固定 breakpoint / aspect ratio</li>
<li>元件預設尺寸</li>
<li>跨狀態的視覺差異（<code>.expanded</code>、<code>.loading</code>）</li>
</ul>
<p>寫成 CSS variable + class toggle、JS 只負責加減 class。</p>
<h3 id="js-assisted必須-runtime-才能知道">JS-assisted：必須 runtime 才能知道</h3>
<ul>
<li>Form 高度（隨字型 / line-height 變動）</li>
<li>Container 寬度（隨 viewport / sidebar 變動）</li>
<li>Scroll position</li>
<li>元素的 bounding rect</li>
</ul>
<p>JS 量測後<strong>寫回 CSS variable</strong>、CSS 仍然只讀變數：</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">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></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">setProperty</span><span class="p">(</span><span class="s1">&#39;--form-height&#39;</span><span class="p">,</span> <span class="sb">`</span><span class="si">${</span><span class="nx">formHeight</span><span class="si">}</span><span class="sb">px`</span><span class="p">);</span></span></span></code></pre></div>




<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></code></pre></div><p>CSS 不知道值怎麼來的、只知道讀 var — 換 framework / 換量測方式時、CSS 不動。</p>
<hr>
<h2 id="模式-1class-toggle-取代-inline-style">模式 1：Class toggle 取代 inline style</h2>
<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="c1">// JS 直接設 inline style + important
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kd">function</span> <span class="nx">showScope</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">scope</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">setProperty</span><span class="p">(</span><span class="s1">&#39;display&#39;</span><span class="p">,</span> <span class="s1">&#39;block&#39;</span><span class="p">,</span> <span class="s1">&#39;important&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="kd">function</span> <span class="nx">hideScope</span><span class="p">()</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="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">setProperty</span><span class="p">(</span><span class="s1">&#39;display&#39;</span><span class="p">,</span> <span class="s1">&#39;none&#39;</span><span class="p">,</span> <span class="s1">&#39;important&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>DevTools 看到 inline style + important、不知道為什麼、難 debug。</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="kd">function</span> <span class="nx">setScope</span><span class="p">(</span><span class="nx">visible</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nx">scope</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">toggle</span><span class="p">(</span><span class="s1">&#39;is-visible&#39;</span><span class="p">,</span> <span class="nx">visible</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span></span></span></code></pre></div>




<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">display</span><span class="p">:</span> <span class="kc">none</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">scope</span><span class="p">.</span><span class="nc">is-visible</span> <span class="p">{</span> <span class="k">display</span><span class="p">:</span> <span class="kc">block</span><span class="p">;</span> <span class="p">}</span></span></span></code></pre></div><p>樣式留在 CSS、JS 只 toggle state。改視覺只動 CSS、改 logic 只動 JS。</p>
<hr>
<h2 id="模式-2css-layers-取代-specificity-戰">模式 2：CSS Layers 取代 specificity 戰</h2>
<h3 id="反例-1">反例</h3>
<p>自家規則被 vendor 的 <code>.pagefind-ui .target</code> 蓋過、寫 <code>.parent .container .target</code> 加 specificity、再不行加 <code>!important</code>。</p>
<h3 id="對例-1">對例</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="p">@</span><span class="k">layer</span> <span class="nt">vendor</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="p">@</span><span class="k">import</span> <span class="nt">url</span><span class="o">(</span><span class="s1">&#39;vendor/pagefind.css&#39;</span><span class="o">)</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span>
</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"><span class="c">/* 自家規則 unlayered → 自動贏所有 layered 規則 */</span>
</span></span><span class="line"><span class="ln">6</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="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">brand</span><span class="p">);</span> <span class="p">}</span></span></span></code></pre></div><p><code>@layer vendor</code> 把 vendor CSS 放進低優先級的 layer、自家 unlayered 規則自動贏。再也不用打 specificity 戰。</p>
<p><code>@layer</code> 在 Chrome 99+ / Firefox 97+ / Safari 15.4+ 全部支援（2022+）。</p>
<hr>
<h2 id="模式-3css-variable-單一定義位置">模式 3：CSS Variable 單一定義位置</h2>
<h3 id="反例-2">反例</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="p">:</span><span class="nd">root</span> <span class="p">{</span> <span class="nv">--gap</span><span class="p">:</span> <span class="mi">16</span><span class="kt">px</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">results</span> <span class="p">{</span> <span class="nv">--gap</span><span class="p">:</span> <span class="mi">16</span><span class="kt">px</span><span class="p">;</span> <span class="k">padding</span><span class="p">:</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">3</span><span class="cl"><span class="p">.</span><span class="nc">scope</span> <span class="p">{</span> <span class="nv">--gap</span><span class="p">:</span> <span class="mi">16</span><span class="kt">px</span><span class="p">;</span> <span class="k">margin-top</span><span class="p">:</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">4</span><span class="cl"><span class="c">/* 三處定義、改一個地方漏改 */</span></span></span></code></pre></div><h3 id="對例-2">對例</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="p">:</span><span class="nd">root</span> <span class="p">{</span> <span class="nv">--gap</span><span class="p">:</span> <span class="mi">16</span><span class="kt">px</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">results</span> <span class="p">{</span> <span class="k">padding</span><span class="p">:</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">3</span><span class="cl"><span class="p">.</span><span class="nc">scope</span> <span class="p">{</span> <span class="k">margin-top</span><span class="p">:</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></code></pre></div><p>定義集中 <code>:root</code>（global）、<code>.page-search</code>（page-scoped）、或 <code>.pagefind-ui</code>（component-scoped）— <strong>挑最窄能涵蓋所有用途的 selector</strong>。其他地方只引用、不重新定義。</p>
<p>JS 寫 variable 也寫到同個 selector：</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="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">setProperty</span><span class="p">(</span><span class="s1">&#39;--form-height&#39;</span><span class="p">,</span> <span class="s1">&#39;...&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// 而不是 form.style.setProperty(...) 在 form 上設
</span></span></span></code></pre></div><hr>
<h2 id="模式-4inline-程式碼超過-30-行就拆檔">模式 4：Inline 程式碼超過 30 行就拆檔</h2>
<h3 id="反例-3">反例</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">style</span><span class="p">&gt;</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="err">...</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">results</span> <span class="p">{</span> <span class="err">...</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="c">/* ... 50 行 */</span>
</span></span><span class="line"><span class="ln">5</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">6</span><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="kd">function</span> <span class="nx">decorate</span><span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="cm">/* ... 80 行 */</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div><p>問題：沒 syntax highlight、沒 minify、沒 fingerprint cache-bust、改一行整個 HTML reload。</p>
<h3 id="對例-3">對例</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">{{ $css := resources.Get &#34;css/search.css&#34; | minify | fingerprint }}
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;{{ $css.RelPermalink }}&#34;</span><span class="p">&gt;</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">{{ $js := resources.Get &#34;js/search.js&#34; | minify | fingerprint }}
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;{{ $js.RelPermalink }}&#34;</span> <span class="na">defer</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div><p>獨立檔案 → IDE 支援、build pipeline 處理 minify / fingerprint、cache-bust 自動。</p>
<hr>
<h2 id="模式-5runtime-量測模式統一">模式 5：Runtime 量測模式統一</h2>
<p>對齊基準上的尺寸值要嘛全寫死、要嘛全量測、不要混搭。</p>
<h3 id="反例-4">反例</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 高度寫死、gap 寫死、scope 用 measured 值 */</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></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">top</span><span class="p">:</span> <span class="nb">calc</span><span class="p">(</span><span class="mi">72</span><span class="kt">px</span> <span class="o">+</span> <span class="mi">16</span><span class="kt">px</span> <span class="o">+</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">scope</span><span class="o">-</span><span class="n">measured</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Form 高度其實會隨字型變動 → 70px 或 76px → scope 跑位。</p>
<h3 id="對例-a全寫死">對例 A：全寫死</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="p">.</span><span class="nc">form</span> <span class="p">{</span> <span class="k">height</span><span class="p">:</span> <span class="mi">72</span><span class="kt">px</span><span class="p">;</span> <span class="p">}</span>  <span class="c">/* 強制固定高度 */</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="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">h</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></code></pre></div><p>Form 強制固定高度、所有變數都是已知。</p>
<h3 id="對例-b全量測">對例 B：全量測</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="kd">function</span> <span class="nx">recalc</span><span class="p">()</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">fH</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></span><span class="line"><span class="ln">3</span><span class="cl">  <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></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">setProperty</span><span class="p">(</span><span class="s1">&#39;--form-h&#39;</span><span class="p">,</span> <span class="sb">`</span><span class="si">${</span><span class="nx">fH</span><span class="si">}</span><span class="sb">px`</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">setProperty</span><span class="p">(</span><span class="s1">&#39;--gap&#39;</span><span class="p">,</span> <span class="sb">`</span><span class="si">${</span><span class="nx">gap</span><span class="si">}</span><span class="sb">px`</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">new</span> <span class="nx">ResizeObserver</span><span class="p">(</span><span class="nx">recalc</span><span class="p">).</span><span class="nx">observe</span><span class="p">(</span><span class="nx">form</span><span class="p">);</span></span></span></code></pre></div>




<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">h</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></code></pre></div><p>全部 runtime 算、CSS 只讀變數。</p>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1搜尋框背景色客製">範例 1：搜尋框背景色客製</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="nx">input</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">setProperty</span><span class="p">(</span><span class="s1">&#39;background&#39;</span><span class="p">,</span> <span class="s1">&#39;#fff&#39;</span><span class="p">,</span> <span class="s1">&#39;important&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">input</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">setProperty</span><span class="p">(</span><span class="s1">&#39;color&#39;</span><span class="p">,</span> <span class="s1">&#39;#000&#39;</span><span class="p">,</span> <span class="s1">&#39;important&#39;</span><span class="p">);</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="k">layer</span> <span class="nt">vendor</span> <span class="p">{</span> <span class="p">@</span><span class="k">import</span> <span class="s1">&#39;pagefind.css&#39;</span><span class="p">;</span> <span class="p">}</span>
</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 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">bg</span><span class="p">);</span> <span class="k">color</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="kc">text</span><span class="p">);</span> <span class="p">}</span></span></span></code></pre></div><p>JS 不需要參與、純 CSS 解。</p>
<h3 id="範例-2跨-viewport-的-sidebar-切換">範例 2：跨 viewport 的 sidebar 切換</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="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;resize&#39;</span><span class="p">,</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="k">if</span> <span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span> <span class="o">&gt;=</span> <span class="mi">1400</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">sidebar</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="s1">&#39;block&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nx">sidebar</span><span class="p">.</span><span class="nx">style</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">6</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">});</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">sidebar</span> <span class="p">{</span> <span class="k">display</span><span class="p">:</span> <span class="kc">none</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="k">media</span> <span class="o">(</span><span class="nt">min-width</span><span class="o">:</span> <span class="nt">1400px</span><span class="o">)</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">sidebar</span> <span class="p">{</span> <span class="k">display</span><span class="p">:</span> <span class="kc">block</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>值（1400）能 build-time 定下來 → CSS media query 直接寫、不需要 JS resize listener。</p>
<hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>寫樣式相關 code 前：</p>
<ul>
<li><input disabled="" type="checkbox"> 我有沒有問「這個值能不能 build-time 定下來」？</li>
<li><input disabled="" type="checkbox"> 我有沒有用 <code>!important</code> / inline <code>setProperty(..., 'important')</code>？（如果有 → 換成 class toggle）</li>
<li><input disabled="" type="checkbox"> 我有沒有跟 vendor CSS 打 specificity 戰？（如果有 → 用 <code>@layer</code>）</li>
<li><input disabled="" type="checkbox"> CSS variable 是不是只在一個地方定義？</li>
<li><input disabled="" type="checkbox"> Inline <code>&lt;style&gt;</code> / <code>&lt;script&gt;</code> 是不是 &lt; 30 行？（超過就拆檔）</li>
<li><input disabled="" type="checkbox"> Runtime 量測跟 hardcoded 值在同一個對齊基準上、是不是只用了一邊？</li>
</ul>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code>）：</p>
<ul>
<li><a href="/blog/report/css-only-vs-js-assisted/" data-link-title="排版精度的工具選擇：CSS-only vs JS-assisted" data-link-desc="CSS 適合 build-time 可決定的 layout、JS 適合 runtime 才知道的尺寸與 DOM 移動。混淆兩者會讓 layout 跟 framework 渲染週期競爭。本文展開選擇規則。">css-only-vs-js-assisted</a> — 排版精度的工具選擇</li>
<li><a href="/blog/report/class-toggle-over-important/" data-link-title="以 class toggle 取代 inline `display: none !important`" data-link-desc="JS 用 `el.style.setProperty(&#39;display&#39;, &#39;none&#39;, &#39;important&#39;)` 是低層次 hack。在 CSS Layers 環境下、用語意化 class &#43; JS toggle 可以更乾淨、更易 debug。">class-toggle-over-important</a> — class toggle 取代 inline <code>display:none !important</code></li>
<li><a href="/blog/report/css-layers-over-specificity/" data-link-title="CSS Layers 取代 specificity 戰" data-link-desc="用 @import url(&#39;vendor.css&#39;) layer(vendor) 把外部組件 CSS 包進低權層、自家 CSS 留在 unlayered 自動贏 — 不論 specificity 數值。本文展開取代 !important 與雙寫的方法。">css-layers-over-specificity</a> — CSS Layers 取代 specificity 戰</li>
<li><a href="/blog/report/css-variable-single-location/" data-link-title="CSS 變數定義位置統一" data-link-desc="CSS 變數一次定義在離 root 最近的合適位置、其他地方只引用、不重複宣告。改 token 只動一處、避免散落多處難同步。">css-variable-single-location</a> — CSS 變數定義位置統一</li>
<li><a href="/blog/report/extract-css-js-files/" data-link-title="CSS / JS 拆出獨立檔案" data-link-desc="Hugo template 內 inline CSS / JS 超過 30 行就值得拆檔、走 resources pipeline。本文展開拆檔的理由、步驟、與得益。">extract-css-js-files</a> — CSS / JS 拆出獨立檔案</li>
<li><a href="/blog/report/runtime-measurement-unification/" data-link-title="runtime 量測模式統一" data-link-desc="對齊基準上的所有元素、要嘛全部寫死、要嘛全部用 ResizeObserver 量測 — 不要混搭。混搭時某些字型 / theme 變化會打破對齊、且難以重現。">runtime-measurement-unification</a> — runtime 量測模式統一</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.1.0</p>
]]></content:encoded></item><item><title>Data Flow and Filter Composition — Filter × Source 層錯位與五策略</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/</guid><description>&lt;p>設計 filter / sort / count / transform 等 stream 操作時、確保操作位置跟資料源同層、避免層錯位產生 silent 缺口。原則跨 UI / 後端 / 演算法管線通用 — 不只是前端問題。&lt;/p>
&lt;p>適用：前端 paginated UI 加 filter、後端 API + middleware filter、演算法 pipeline 加 transform、map-reduce 加 post-filter、資料庫 materialized view 加 query。
不適用：純運算演算法（沒有 stream / 沒有 materialization 概念）、純 React state 管理（沒有外部 source）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋層錯位識別、五策略選擇、跨領域範例、playwright 驗證方法。&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;code>forEach(el =&amp;gt; el.hidden = !matches(el))&lt;/code>&lt;/td>
 &lt;td>停 — 確認 source 是不是分批 / streaming&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Source 是 &lt;code>pagefind.search()&lt;/code> / &lt;code>paginatedFetch()&lt;/code> / &lt;code>for await&lt;/code>&lt;/td>
 &lt;td>filter 必須跟 source 同層、不能在 view 層後處理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>「filter 後 0 筆但 source 還有未載入」可能發生&lt;/td>
 &lt;td>必須補自動續抓 / 推進 query / 誠實 UX&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Backend middleware / response wrapper 加 filter&lt;/td>
 &lt;td>推進 ORM query / SQL &lt;code>WHERE&lt;/code>、不在 response 後&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>演算法 pipeline 末端 filter&lt;/td>
 &lt;td>推進 pipeline stage 內、stream-aware&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Map-reduce 完成後加 post-filter&lt;/td>
 &lt;td>推進 map / reduce 階段&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>「畫面 / 結果對了但邊界 case 怪」&lt;/td>
 &lt;td>識別這是層錯位、不是 bug 修補能解&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼-filter--source-是個結構性議題">為什麼 filter × source 是個結構性議題&lt;/h2>
&lt;p>Filter 操作的定義是「從 stream 中過濾出符合條件的元素」 — &lt;strong>stream&lt;/strong> 是隱含的對象。當 stream 被分層 materialize 時、filter 套在哪一層、決定它能「看見」的元素範圍：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層&lt;/th>
 &lt;th>能看到的範圍&lt;/th>
 &lt;th>filter 結果的語意&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Source 層&lt;/td>
 &lt;td>完整 stream&lt;/td>
 &lt;td>「stream 中所有符合的」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Materialization 中&lt;/td>
 &lt;td>已 materialize 的部分&lt;/td>
 &lt;td>「目前載入的符合的」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>下游（view / response）&lt;/td>
 &lt;td>Materialized 之後 + downstream filter 之前的子集&lt;/td>
 &lt;td>「下游可見的子集中符合的」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>使用者 / 呼叫者意圖的「filter」通常是第一層（stream 全集）— 但寫程式當下手邊的對象通常是第三層（已 materialize 的 subset）。&lt;strong>寫起來最便利的位置 ≠ 對齊意圖的位置&lt;/strong>。&lt;/p>
&lt;p>這是 &lt;a href="https://tarrragon.github.io/blog/report/ease-of-writing-vs-intent-alignment/" data-link-title="寫作便利度跟意圖對齊反相關" data-link-desc="寫程式時最容易寫出的版本、通常是離意圖最遠的版本。便利度建立在「現有上下文 / 已 materialize 資料 / 已存在 API」上、而意圖對齊需要找到正確的層、處理上游、跨抽象層 — 兩者方向相反。識別這個反相關 = 識別自己掉進「容易寫的陷阱」。">#67 寫作便利度跟意圖對齊反相關&lt;/a> 在 stream 操作上的具體展現。&lt;/p></description><content:encoded><![CDATA[<p>設計 filter / sort / count / transform 等 stream 操作時、確保操作位置跟資料源同層、避免層錯位產生 silent 缺口。原則跨 UI / 後端 / 演算法管線通用 — 不只是前端問題。</p>
<p>適用：前端 paginated UI 加 filter、後端 API + middleware filter、演算法 pipeline 加 transform、map-reduce 加 post-filter、資料庫 materialized view 加 query。
不適用：純運算演算法（沒有 stream / 沒有 materialization 概念）、純 React state 管理（沒有外部 source）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋層錯位識別、五策略選擇、跨領域範例、playwright 驗證方法。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>即將寫 <code>forEach(el =&gt; el.hidden = !matches(el))</code></td>
          <td>停 — 確認 source 是不是分批 / streaming</td>
      </tr>
      <tr>
          <td>Source 是 <code>pagefind.search()</code> / <code>paginatedFetch()</code> / <code>for await</code></td>
          <td>filter 必須跟 source 同層、不能在 view 層後處理</td>
      </tr>
      <tr>
          <td>「filter 後 0 筆但 source 還有未載入」可能發生</td>
          <td>必須補自動續抓 / 推進 query / 誠實 UX</td>
      </tr>
      <tr>
          <td>Backend middleware / response wrapper 加 filter</td>
          <td>推進 ORM query / SQL <code>WHERE</code>、不在 response 後</td>
      </tr>
      <tr>
          <td>演算法 pipeline 末端 filter</td>
          <td>推進 pipeline stage 內、stream-aware</td>
      </tr>
      <tr>
          <td>Map-reduce 完成後加 post-filter</td>
          <td>推進 map / reduce 階段</td>
      </tr>
      <tr>
          <td>「畫面 / 結果對了但邊界 case 怪」</td>
          <td>識別這是層錯位、不是 bug 修補能解</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼-filter--source-是個結構性議題">為什麼 filter × source 是個結構性議題</h2>
<p>Filter 操作的定義是「從 stream 中過濾出符合條件的元素」 — <strong>stream</strong> 是隱含的對象。當 stream 被分層 materialize 時、filter 套在哪一層、決定它能「看見」的元素範圍：</p>
<table>
  <thead>
      <tr>
          <th>層</th>
          <th>能看到的範圍</th>
          <th>filter 結果的語意</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source 層</td>
          <td>完整 stream</td>
          <td>「stream 中所有符合的」</td>
      </tr>
      <tr>
          <td>Materialization 中</td>
          <td>已 materialize 的部分</td>
          <td>「目前載入的符合的」</td>
      </tr>
      <tr>
          <td>下游（view / response）</td>
          <td>Materialized 之後 + downstream filter 之前的子集</td>
          <td>「下游可見的子集中符合的」</td>
      </tr>
  </tbody>
</table>
<p>使用者 / 呼叫者意圖的「filter」通常是第一層（stream 全集）— 但寫程式當下手邊的對象通常是第三層（已 materialize 的 subset）。<strong>寫起來最便利的位置 ≠ 對齊意圖的位置</strong>。</p>
<p>這是 <a href="/blog/report/ease-of-writing-vs-intent-alignment/" data-link-title="寫作便利度跟意圖對齊反相關" data-link-desc="寫程式時最容易寫出的版本、通常是離意圖最遠的版本。便利度建立在「現有上下文 / 已 materialize 資料 / 已存在 API」上、而意圖對齊需要找到正確的層、處理上游、跨抽象層 — 兩者方向相反。識別這個反相關 = 識別自己掉進「容易寫的陷阱」。">#67 寫作便利度跟意圖對齊反相關</a> 在 stream 操作上的具體展現。</p>
<hr>
<h2 id="跨領域同個結構五個情境">跨領域：同個結構、五個情境</h2>
<h3 id="情境-1前端-ui--pagefind-paginated-search">情境 1：前端 UI + Pagefind paginated search</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="c1">// 反例：post-filter on view layer
</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">all</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">pagefind</span><span class="p">.</span><span class="nx">search</span><span class="p">(</span><span class="nx">query</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">all</span><span class="p">.</span><span class="nx">results</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">start</span><span class="p">,</span> <span class="nx">start</span> <span class="o">+</span> <span class="mi">10</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">render</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><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 class="nx">forEach</span><span class="p">(</span><span class="nx">el</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nx">el</span><span class="p">.</span><span class="nx">hidden</span> <span class="o">=</span> <span class="o">!</span><span class="nx">matches</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span>  <span class="c1">// view 層 filter
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="p">});</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// 第二批全 hidden、使用者看到「load more 沒效果」
</span></span></span></code></pre></div><h3 id="情境-2後端-api--orm-middleware">情境 2：後端 API + ORM middleware</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 反例：middleware 在 pagination 之後 filter</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s2">&#34;/posts&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">posts</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">page</span> <span class="o">=</span> <span class="n">Post</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">paginate</span><span class="p">(</span><span class="n">page</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">per_page</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">page</span><span class="o">.</span><span class="n">items</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">author</span> <span class="o">==</span> <span class="s2">&#34;author_x&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="c1"># 漏掉沒在這頁的符合項</span></span></span></code></pre></div><h3 id="情境-3async-iterator--taken">情境 3：Async iterator + take(N)</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 反例：先 take 後 filter</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">itertools</span><span class="o">.</span><span class="n">islice</span><span class="p">(</span><span class="n">stream</span><span class="p">(),</span> <span class="mi">100</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">filtered</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">items</span> <span class="k">if</span> <span class="n">matches</span><span class="p">(</span><span class="n">x</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># stream 後面可能還有符合的、但被 take 100 切斷了</span></span></span></code></pre></div><h3 id="情境-4map-reduce--post-reduce-filter">情境 4：Map-reduce + post-reduce filter</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">[shards] → [map output] → [reduce]
</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">                         [filter]  ← 已是 reduce 後的結果</span></span></code></pre></div><p>Filter 應該在 map 階段（per-shard）或 reduce 內、不是 reduce 後。</p>
<h3 id="情境-5materialized-view--select">情境 5：Materialized view + SELECT</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- 反例：在 stale view 上 filter
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">posts_summary</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">author_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">42</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="c1">-- view 可能是某個時點的 snapshot、漏掉之後寫入的 posts
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="c1">-- 對例：filter 推進原表
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">posts</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">author_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">42</span><span class="p">;</span></span></span></code></pre></div><p><strong>五個情境共用結構</strong>：source 是分層 materialize 的、filter 套在下游 → silent 缺口。</p>
<hr>
<h2 id="五種解法策略">五種解法策略</h2>
<p>詳細展開見 <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>
<table>
  <thead>
      <tr>
          <th>策略</th>
          <th>一句話</th>
          <th>對 source 的需求</th>
          <th>工程量</th>
          <th>UX 影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>A</td>
          <td>把 filter 推進 source 的 query</td>
          <td>必須支援該 filter 條件</td>
          <td>中-高</td>
          <td>透明（無感）</td>
      </tr>
      <tr>
          <td>B</td>
          <td>自動續抓直到湊滿 N 個 match</td>
          <td>任何分批 source</td>
          <td>中</td>
          <td>透明（稍慢）</td>
      </tr>
      <tr>
          <td>C</td>
          <td>預先建獨立 index（每種 mode 一份）</td>
          <td>能控 source 的 build pipeline</td>
          <td>高</td>
          <td>透明（最快）</td>
      </tr>
      <tr>
          <td>D</td>
          <td>誠實 UX 顯示「已掃 N / 命中 K」</td>
          <td>任何 source</td>
          <td>低</td>
          <td>顯眼（多按鈕）</td>
      </tr>
      <tr>
          <td>E</td>
          <td>明示語意縮小（filter 範圍 = 已載入）</td>
          <td>任何 source</td>
          <td>最低</td>
          <td>隱性語意縮小</td>
      </tr>
  </tbody>
</table>
<p>選擇順序：<strong>A → C → B → D → E</strong>（不寫不告知的 silent 縮小、那是反模式）。</p>
<p>對應的 pattern 卡片：<a href="/blog/report/pattern-fetch-until-quota/" data-link-title="Pattern：自動續抓直到湊滿 quota" data-link-desc="Pattern 卡片：分批 source &#43; post-filter 時、自動續抓直到湊滿 N 個 match。含上限保護、進度顯示、可中斷三個必要元件。對應 #59 策略 B 的具體實作。">#60 自動續抓</a> / <a href="/blog/report/pattern-query-side-pushdown/" data-link-title="Pattern：把 filter 推進 query 引擎" data-link-desc="Pattern 卡片：把 client-side filter 推進 source 的 query 引擎、由 source 直接回符合的。對應 #59 策略 A 的具體實作。前提是 source capabilities 支援該 filter 條件、否則要評估重 index。">#61 推進 query</a> / <a href="/blog/report/pattern-honest-progress-ui/" data-link-title="Pattern：誠實進度 UX（已掃 N / 命中 K / 共 M）" data-link-desc="Pattern 卡片：當 filter 跟 source 必然有層錯位、用三數字（已掃 N / 命中 K / 共 M）讓使用者看見掃描範圍、避免誤以為「沒命中」。對應 #59 策略 D 的具體實作。">#62 誠實進度 UX</a> / <a href="/blog/report/pattern-multiple-indexes/" data-link-title="Pattern：預先建獨立 index（每種 mode 一份）" data-link-desc="Pattern 卡片：build time 為每種 filter mode 各建一份 source / index、runtime 切換 mode = 切 source。對應 #59 策略 C 的具體實作。前提是能控 source 的 build pipeline、且 mode 數量有限。">#65 多 index</a> / <a href="/blog/report/pattern-explicit-semantic-narrowing/" data-link-title="Pattern：明示語意縮小（不承諾全集）" data-link-desc="Pattern 卡片：當 filter 必然只能在 subset 上做、明確告訴使用者「這只在已載入範圍內找」、不假裝是全集 filter。對應 #59 策略 E 的具體實作。重點是「明示」、silent 縮小是反模式。">#66 明示語意縮小</a></p>
<hr>
<h2 id="三變數決定策略選擇">三變數決定策略選擇</h2>
<p>選 A / B / C / D / E 看三個變數：</p>
<h3 id="變數-1source-capabilities">變數 1：Source capabilities</h3>
<p>Source 支援哪些 server-side filter？</p>
<ul>
<li>支援該 filter 條件 → A 最優</li>
<li>不支援、能控 build → 評估 C</li>
<li>都不行 → B / D / E</li>
</ul>
<h3 id="變數-2match-密度">變數 2：Match 密度</h3>
<p>每抓一批、預期多少筆 match？</p>
<ul>
<li>高密度（每批 ≥ 1 個 match）→ B 自動續抓 OK</li>
<li>稀疏（要抓很多批才湊到一個）→ B 會拉爆、用 D / E</li>
<li>不可預期 → 加上限保護的 B + fallback 到 D</li>
</ul>
<h3 id="變數-3ux-容忍度">變數 3：UX 容忍度</h3>
<p>使用者能接受多顯眼的「掃描範圍」UX？</p>
<ul>
<li>完全不行（filter 是核心互動）→ A / C</li>
<li>可以顯示三數字 → D</li>
<li>一次性文字告知就行 → E</li>
</ul>
<hr>
<h2 id="playwright-驗證-filter--source-行為">Playwright 驗證 filter × source 行為</h2>
<p>寫完 filter 後、用 playwright 驗證是否有層錯位 silent 缺口。</p>
<h3 id="驗證-1load-more-後-filter-後是否該有結果">驗證 1：「Load more 後 filter 後是否該有結果」</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="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">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="s1">&#39;[data-scope=&#34;title&#34;]&#39;</span><span class="p">);</span>  <span class="c1">// 選 title-only
</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="kr">const</span> <span class="nx">before</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nx">loaded</span><span class="o">:</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">$$eval</span><span class="p">(</span><span class="s1">&#39;.result&#39;</span><span class="p">,</span> <span class="nx">els</span> <span class="p">=&gt;</span> <span class="nx">els</span><span class="p">.</span><span class="nx">length</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nx">visible</span><span class="o">:</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">$$eval</span><span class="p">(</span><span class="s1">&#39;.result:not([hidden])&#39;</span><span class="p">,</span> <span class="nx">els</span> <span class="p">=&gt;</span> <span class="nx">els</span><span class="p">.</span><span class="nx">length</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="p">};</span>
</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 class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="s1">&#39;button.load-more&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForTimeout</span><span class="p">(</span><span class="mi">500</span><span class="p">);</span>
</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 class="kr">const</span> <span class="nx">after</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nx">loaded</span><span class="o">:</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">$$eval</span><span class="p">(</span><span class="s1">&#39;.result&#39;</span><span class="p">,</span> <span class="nx">els</span> <span class="p">=&gt;</span> <span class="nx">els</span><span class="p">.</span><span class="nx">length</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nx">visible</span><span class="o">:</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">$$eval</span><span class="p">(</span><span class="s1">&#39;.result:not([hidden])&#39;</span><span class="p">,</span> <span class="nx">els</span> <span class="p">=&gt;</span> <span class="nx">els</span><span class="p">.</span><span class="nx">length</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="p">};</span>
</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 class="c1">// 層錯位徵兆：loaded 增加、visible 沒增加
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span>  <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nx">deltaLoaded</span><span class="o">:</span> <span class="nx">after</span><span class="p">.</span><span class="nx">loaded</span> <span class="o">-</span> <span class="nx">before</span><span class="p">.</span><span class="nx">loaded</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="nx">deltaVisible</span><span class="o">:</span> <span class="nx">after</span><span class="p">.</span><span class="nx">visible</span> <span class="o">-</span> <span class="nx">before</span><span class="p">.</span><span class="nx">visible</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="nx">isSilentGap</span><span class="o">:</span> <span class="nx">after</span><span class="p">.</span><span class="nx">loaded</span> <span class="o">&gt;</span> <span class="nx">before</span><span class="p">.</span><span class="nx">loaded</span> <span class="o">&amp;&amp;</span> <span class="nx">after</span><span class="p">.</span><span class="nx">visible</span> <span class="o">===</span> <span class="nx">before</span><span class="p">.</span><span class="nx">visible</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="驗證-2稀疏-case-是否拉爆">驗證 2：「稀疏 case 是否拉爆」</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="c1">// 用一個極少 match 的 query 觸發 B 策略
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><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=very_rare_keyword&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="s1">&#39;[data-scope=&#34;title&#34;]&#39;</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">startTime</span> <span class="o">=</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</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="nx">page</span><span class="p">.</span><span class="nx">waitForSelector</span><span class="p">(</span><span class="s1">&#39;.scan-status&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">timeout</span><span class="o">:</span> <span class="mi">10000</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">// 應該在 5s 內顯示「已掃完、共命中 K 個」、不該無限續抓
</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">const</span> <span class="nx">statusVisible</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;.filter-status&#39;</span><span class="p">).</span><span class="nx">textContent</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// 應該明示 loading / partial / end / empty 之一、不只是 spinner
</span></span></span></code></pre></div><p>寫成 playwright test 固化 — 未來架構改動時 CI 立刻發現 regression（<a href="/blog/report/layout-tests-with-playwright/" data-link-title="用前端測試把排版問題自動化" data-link-desc="排版問題傳統靠人眼檢查、容易遺漏邊界 case。當一個版型被 debug 兩次以上、就值得寫成 playwright 測試把規範固定下來。本文展開測試替代手動檢查的時機。">#15 layout-tests-with-playwright</a>）。</p>
<hr>
<h2 id="設計決策的-checklist">設計決策的 checklist</h2>
<p>寫 filter 之前、跑這份 checklist：</p>
<ul>
<li><input disabled="" type="checkbox"> Source 是不是分批 / streaming / cached / lazy？（<a href="/blog/report/data-source-shape-defines-feature-shape/" data-link-title="資料源的形狀決定 feature 的形狀" data-link-desc="Feature 設計要服從資料源的形狀（一次性 / 分批 / streaming / cached）— 不能憑 UI 想要的形狀去倒推資料層。憑 UI 倒推 = 在錯誤的層解錯誤的問題、產生 #55 層錯位類 bug。">#63 資料源形狀</a>）</li>
<li><input disabled="" type="checkbox"> Filter 的定義域是已載入子集還是 source 全集？（使用者意圖三問、見 <a href="/blog/report/filter-instruction-clarification/" data-link-title="篩選類指令的澄清時機" data-link-desc="「依 X 篩選」這類指令必須先澄清三件事才能寫：定義域（已載入 / 全部 / 子集）、資料分批方式、空狀態的語意。三問跑完才寫、否則必然寫成視覺層 post-filter、撞上 #55 層錯位。">#58</a>）</li>
<li><input disabled="" type="checkbox"> Source 是否支援 server-side filter？（決定能不能用 A）</li>
<li><input disabled="" type="checkbox"> Match 密度可預期嗎？（決定 B 是否可行）</li>
<li><input disabled="" type="checkbox"> 三狀態（loading / empty / end）UX 怎麼區分？（<a href="/blog/report/loading-empty-end-state-distinction/" data-link-title="Loading / Empty / End 三狀態的區分" data-link-desc="「還沒抓」「沒命中」「抓完無更多」三個狀態語意不同、UX 必須區分。共用同個畫面（「空白」或 spinner）會讓使用者無法判斷下一步。本文展開三狀態的內在屬性與 UX 規則。">#57</a>）</li>
<li><input disabled="" type="checkbox"> 對於「filter 後 0 筆」的情境、使用者能否區分「沒命中」vs「還沒抓到」？</li>
</ul>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1搜尋頁-title-only-filter">範例 1：搜尋頁 title-only filter</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">// pagefind 分批載入、view 層 post-filter
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <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"> 3</span><span class="cl">  <span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">pagefind</span><span class="p">.</span><span class="nx">search</span><span class="p">(</span><span class="nx">input</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="nx">results</span><span class="p">.</span><span class="nx">results</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">10</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">render</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></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.scope-title&#39;</span><span class="p">).</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <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 class="nx">forEach</span><span class="p">(</span><span class="nx">el</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="kr">const</span> <span class="nx">title</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.title&#39;</span><span class="p">).</span><span class="nx">textContent</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nx">el</span><span class="p">.</span><span class="nx">hidden</span> <span class="o">=</span> <span class="o">!</span><span class="nx">title</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">query</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>第二批 8 筆 title 不含 query → 全 hidden、使用者看到「load more 沒效果」。</p>
<p><strong>對</strong>（策略 C：多 index + 切換）：</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"># Build 階段</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">pagefind --site public --output-subdir _pagefind-all
</span></span><span class="line"><span class="ln">3</span><span class="cl">pagefind --site public --root-selector <span class="s2">&#34;article h1, article h2&#34;</span> --output-subdir _pagefind-title</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">const</span> <span class="nx">indexes</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="nx">all</span><span class="o">:</span> <span class="kr">await</span> <span class="kr">import</span><span class="p">(</span><span class="s1">&#39;/_pagefind-all/pagefind.js&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nx">title</span><span class="o">:</span> <span class="kr">await</span> <span class="kr">import</span><span class="p">(</span><span class="s1">&#39;/_pagefind-title/pagefind.js&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">};</span>
</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 class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <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"> 7</span><span class="cl">  <span class="kr">const</span> <span class="nx">pf</span> <span class="o">=</span> <span class="nx">currentScope</span> <span class="o">===</span> <span class="s1">&#39;title&#39;</span> <span class="o">?</span> <span class="nx">indexes</span><span class="p">.</span><span class="nx">title</span> <span class="o">:</span> <span class="nx">indexes</span><span class="p">.</span><span class="nx">all</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">pf</span><span class="p">.</span><span class="nx">search</span><span class="p">(</span><span class="nx">input</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="c1">// results 已是「該 scope 的全集」、無層錯位
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>  <span class="nx">results</span><span class="p">.</span><span class="nx">results</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">10</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">render</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p><strong>對</strong>（策略 D：誠實進度 UX、保留 view 層 filter）：</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="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;filter-status&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  已掃 <span class="p">&lt;</span><span class="nt">strong</span><span class="p">&gt;</span>24<span class="p">&lt;/</span><span class="nt">strong</span><span class="p">&gt;</span> / <span class="p">&lt;</span><span class="nt">strong</span><span class="p">&gt;</span>~150<span class="p">&lt;/</span><span class="nt">strong</span><span class="p">&gt;</span> 筆 — 命中 <span class="p">&lt;</span><span class="nt">strong</span><span class="p">&gt;</span>3<span class="p">&lt;/</span><span class="nt">strong</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">button</span><span class="p">&gt;</span>再掃一批<span class="p">&lt;/</span><span class="nt">button</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="p">&gt;</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="c1">// view 層 filter 保留、但 UI 顯示掃描範圍 + 提供續抓
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kd">function</span> <span class="nx">updateStatus</span><span class="p">()</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">all</span> <span class="o">=</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">4</span><span class="cl">  <span class="kr">const</span> <span class="nx">visible</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">&#39;.result:not([hidden])&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.scanned&#39;</span><span class="p">).</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">all</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.matched&#39;</span><span class="p">).</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">visible</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="範例-2後端-api-filter">範例 2：後端 API filter</h3>
<p><strong>錯</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s2">&#34;/posts&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">list_posts</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">page</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">args</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;page&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">posts</span> <span class="o">=</span> <span class="n">Post</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">paginate</span><span class="p">(</span><span class="n">page</span><span class="o">=</span><span class="n">page</span><span class="p">,</span> <span class="n">per_page</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">if</span> <span class="n">author</span> <span class="o">:=</span> <span class="n">request</span><span class="o">.</span><span class="n">args</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;author&#39;</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="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">posts</span><span class="o">.</span><span class="n">items</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">author</span> <span class="o">==</span> <span class="n">author</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">return</span> <span class="n">posts</span><span class="o">.</span><span class="n">items</span></span></span></code></pre></div><p>中間的 list comprehension 在 pagination 之後 filter — 漏掉沒在這頁的符合項。</p>
<p><strong>對</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s2">&#34;/posts&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">list_posts</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">query</span> <span class="o">=</span> <span class="n">Post</span><span class="o">.</span><span class="n">objects</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">if</span> <span class="n">author</span> <span class="o">:=</span> <span class="n">request</span><span class="o">.</span><span class="n">args</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;author&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="n">query</span> <span class="o">=</span> <span class="n">query</span><span class="o">.</span><span class="n">filter_by</span><span class="p">(</span><span class="n">author</span><span class="o">=</span><span class="n">author</span><span class="p">)</span>  <span class="c1"># 推進 ORM</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">page</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">args</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;page&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">return</span> <span class="n">query</span><span class="o">.</span><span class="n">paginate</span><span class="p">(</span><span class="n">page</span><span class="o">=</span><span class="n">page</span><span class="p">,</span> <span class="n">per_page</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span><span class="o">.</span><span class="n">items</span></span></span></code></pre></div><p>Filter 在 query 層、pagination 在 filter 之後、無層錯位。</p>
<hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>寫 filter / sort / count / transform 前：</p>
<ul>
<li><input disabled="" type="checkbox"> 我有沒有問「這個操作的對象是哪一層的 stream」？</li>
<li><input disabled="" type="checkbox"> Source 是分批的嗎？是 → filter 必須同層或推進上游</li>
<li><input disabled="" type="checkbox"> 寫了 view 層 filter？檢查：稀疏 case 會不會 silent 失敗？</li>
<li><input disabled="" type="checkbox"> 用了 B（自動續抓）？有沒有 MAX_BATCHES + MAX_TIME_MS 上限保護？</li>
<li><input disabled="" type="checkbox"> UX 能否區分「載入中 / 沒命中 / 還沒抓到 / 抓完了」四狀態？</li>
<li><input disabled="" type="checkbox"> Playwright 驗證有沒有覆蓋「稀疏 case」「load more 後 visible 是否變」？</li>
</ul>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>問題分析：</p>
<ul>
<li><a href="/blog/report/view-layer-filter-vs-source-layer/" data-link-title="Filter 與 Source 的抽象層錯位" data-link-desc="Filter 必須跟它過濾的資料源在同一層運作。視覺層的 filter 套在資料層分批產出的 source 上、會在「一筆」的定義上產生語意縫 — 使用者要的「全部符合」變成「目前載入的符合」、然後 silent 失敗。本文展開層錯位的識別與糾正。">#55 Filter 與 Source 的抽象層錯位</a> — 根因</li>
<li><a href="/blog/report/visual-completion-vs-functional-completion/" data-link-title="視覺完成 ≠ 功能完成" data-link-desc="「畫面對了」是視覺驗收訊號、不是功能驗收訊號。視覺完成早於功能完成、容易掩蓋語意缺口。本文展開兩者的區分與識別「畫面對但功能漏」的訊號。">#56 視覺完成 ≠ 功能完成</a> — 「畫面對」是低資訊量訊號</li>
<li><a href="/blog/report/loading-empty-end-state-distinction/" data-link-title="Loading / Empty / End 三狀態的區分" data-link-desc="「還沒抓」「沒命中」「抓完無更多」三個狀態語意不同、UX 必須區分。共用同個畫面（「空白」或 spinner）會讓使用者無法判斷下一步。本文展開三狀態的內在屬性與 UX 規則。">#57 Loading / Empty / End 三狀態的區分</a> — UX 落地</li>
</ul>
<p>指令澄清（在 requirement-protocol skill）：</p>
<ul>
<li><a href="/blog/report/filter-instruction-clarification/" data-link-title="篩選類指令的澄清時機" data-link-desc="「依 X 篩選」這類指令必須先澄清三件事才能寫：定義域（已載入 / 全部 / 子集）、資料分批方式、空狀態的語意。三問跑完才寫、否則必然寫成視覺層 post-filter、撞上 #55 層錯位。">#58 篩選類指令的澄清時機</a> — 三問模板</li>
</ul>
<p>解法策略：</p>
<ul>
<li><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> — 總覽</li>
<li><a href="/blog/report/pattern-fetch-until-quota/" data-link-title="Pattern：自動續抓直到湊滿 quota" data-link-desc="Pattern 卡片：分批 source &#43; post-filter 時、自動續抓直到湊滿 N 個 match。含上限保護、進度顯示、可中斷三個必要元件。對應 #59 策略 B 的具體實作。">#60-#62, #65-#66 五張 Pattern 卡片</a> — 各策略具體實作</li>
</ul>
<p>抽象原則：</p>
<ul>
<li><a href="/blog/report/data-source-shape-defines-feature-shape/" data-link-title="資料源的形狀決定 feature 的形狀" data-link-desc="Feature 設計要服從資料源的形狀（一次性 / 分批 / streaming / cached）— 不能憑 UI 想要的形狀去倒推資料層。憑 UI 倒推 = 在錯誤的層解錯誤的問題、產生 #55 層錯位類 bug。">#63 資料源的形狀決定 feature 的形狀</a> — 形狀是硬約束</li>
<li><a href="/blog/report/compose-feature-at-source-layer/" data-link-title="Feature 操作要跟 Source 同層合成" data-link-desc="Filter / sort / count / transform / search 是 stream 操作、必須跟 stream 的 materialization 同層或更上游合成。在下游做 = 操作 subset 不是 stream。本原則跨前端 UI、後端 API、演算法管線通用、不只是視覺層 vs 資料層。">#64 Feature 操作要跟 Source 同層合成</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</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> — 驗收策略</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>DOM Topology First — 寫 CSS 前先確認 DOM 結構</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/dom-topology-first/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/dom-topology-first/</guid><description>&lt;p>寫 CSS 規則之前、先讀真實 DOM tree — class name 是約定、不是結構保證。Selector 設計從最精準起步、有證據再放寬。&lt;/p>
&lt;p>適用：寫 / 改 CSS 規則、設計 JS query selector、判斷是否該改 layout 結構。
不適用：純邏輯演算法（沒有 DOM）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋 DOM 量測方法、selector 三維度設計、四種起點的取捨。&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>即將寫 CSS 規則但只看過 class name、沒看過真實 DOM&lt;/td>
 &lt;td>playwright 量 ancestor chain&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Selector 命中超出預期的元素&lt;/td>
 &lt;td>把 selector 加上起點 + 範圍 + 過濾三維度&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>規則寫了但不生效&lt;/td>
 &lt;td>DevTools Computed → 看誰實際贏了&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Class name 含 &lt;code>__inner&lt;/code> &lt;code>__wrapper&lt;/code> 但不確定是直接子節點&lt;/td>
 &lt;td>playwright 讀 parent / child 關係&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>想用 &lt;code>document.querySelectorAll('.target')&lt;/code>&lt;/td>
 &lt;td>先評估「起點要不要從元件根」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼-dom-topology-要先確認">為什麼 DOM topology 要先確認&lt;/h2>
&lt;p>CSS 行為由「規則 + DOM tree + 樣式繼承 + 框架渲染」四個變數共同決定。&lt;strong>靜態推理只能基於假設的 DOM tree&lt;/strong> — 假設錯了、推理就錯。&lt;/p>
&lt;p>Class name 是命名約定 — &lt;code>pagefind-ui__drawer&lt;/code> 看起來像 &lt;code>.pagefind-ui&lt;/code> 的 child，但實際可能是 &lt;code>pagefind-ui__form&lt;/code> 的 child。命名告訴你「這是 drawer」、不告訴你「在哪一層」。&lt;/p>
&lt;p>跳過 DOM 確認的代價：寫了 N 條 CSS 規則、推理為什麼不生效、加 specificity / &lt;code>!important&lt;/code> / &lt;code>display: contents&lt;/code> — 全部基於錯假設。&lt;/p>
&lt;hr>
&lt;h2 id="量-dom-的最小-query">量 DOM 的最小 query&lt;/h2>





&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="c1">// ancestor chain
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&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"> 3&lt;/span>&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">el&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;.target&amp;#39;&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="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">el&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="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"> 6&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"> 7&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"> 8&lt;/span>&lt;span class="cl"> &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="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">10&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>返回值告訴你目標元素在 DOM tree 哪個位置、parent / sibling 是誰。寫 CSS 規則前 30 秒能省掉後續 30 分鐘推理。&lt;/p>
&lt;hr>
&lt;h2 id="selector-設計三維度">Selector 設計三維度&lt;/h2>
&lt;p>精準的 selector = &lt;strong>起點 + 範圍 + 過濾&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>從哪個 DOM 節點開始 query&lt;/td>
 &lt;td>document / 元件根 / 函式參數 / closest()&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>範圍&lt;/td>
 &lt;td>要找直接子節點還是子孫&lt;/td>
 &lt;td>&lt;code>&amp;gt;&lt;/code> 直接子 / &lt;code>&amp;gt; ... &amp;gt; ...&lt;/code> 多層 / 空格 子孫&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>過濾&lt;/td>
 &lt;td>要排除哪些元素 / 已處理的&lt;/td>
 &lt;td>&lt;code>:not()&lt;/code> / &lt;code>[data-processed]&lt;/code> / WeakMap 檢查&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="起點四選一依情境">起點四選一（依情境）&lt;/h2>
&lt;h3 id="起點-adocument-全文件-query">起點 A：Document 全文件 query&lt;/h3>





&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="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;.target&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>用&lt;/strong>：原型期、單例（整頁只一個）、跨元件邊界元素。
&lt;strong>不用&lt;/strong>：production 客製、可能多實例、效能敏感（大頁面）。&lt;/p></description><content:encoded><![CDATA[<p>寫 CSS 規則之前、先讀真實 DOM tree — class name 是約定、不是結構保證。Selector 設計從最精準起步、有證據再放寬。</p>
<p>適用：寫 / 改 CSS 規則、設計 JS query selector、判斷是否該改 layout 結構。
不適用：純邏輯演算法（沒有 DOM）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋 DOM 量測方法、selector 三維度設計、四種起點的取捨。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>即將寫 CSS 規則但只看過 class name、沒看過真實 DOM</td>
          <td>playwright 量 ancestor chain</td>
      </tr>
      <tr>
          <td>Selector 命中超出預期的元素</td>
          <td>把 selector 加上起點 + 範圍 + 過濾三維度</td>
      </tr>
      <tr>
          <td>規則寫了但不生效</td>
          <td>DevTools Computed → 看誰實際贏了</td>
      </tr>
      <tr>
          <td>Class name 含 <code>__inner</code> <code>__wrapper</code> 但不確定是直接子節點</td>
          <td>playwright 讀 parent / child 關係</td>
      </tr>
      <tr>
          <td>想用 <code>document.querySelectorAll('.target')</code></td>
          <td>先評估「起點要不要從元件根」</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼-dom-topology-要先確認">為什麼 DOM topology 要先確認</h2>
<p>CSS 行為由「規則 + DOM tree + 樣式繼承 + 框架渲染」四個變數共同決定。<strong>靜態推理只能基於假設的 DOM tree</strong> — 假設錯了、推理就錯。</p>
<p>Class name 是命名約定 — <code>pagefind-ui__drawer</code> 看起來像 <code>.pagefind-ui</code> 的 child，但實際可能是 <code>pagefind-ui__form</code> 的 child。命名告訴你「這是 drawer」、不告訴你「在哪一層」。</p>
<p>跳過 DOM 確認的代價：寫了 N 條 CSS 規則、推理為什麼不生效、加 specificity / <code>!important</code> / <code>display: contents</code> — 全部基於錯假設。</p>
<hr>
<h2 id="量-dom-的最小-query">量 DOM 的最小 query</h2>





<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">// ancestor chain
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><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"> 3</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"> 4</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">el</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</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"> 6</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"> 7</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"> 8</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</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">10</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>返回值告訴你目標元素在 DOM tree 哪個位置、parent / sibling 是誰。寫 CSS 規則前 30 秒能省掉後續 30 分鐘推理。</p>
<hr>
<h2 id="selector-設計三維度">Selector 設計三維度</h2>
<p>精準的 selector = <strong>起點 + 範圍 + 過濾</strong> 三維度顯式設計、不是「能命中就好」。</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>問題</th>
          <th>答案類型</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>起點</td>
          <td>從哪個 DOM 節點開始 query</td>
          <td>document / 元件根 / 函式參數 / closest()</td>
      </tr>
      <tr>
          <td>範圍</td>
          <td>要找直接子節點還是子孫</td>
          <td><code>&gt;</code> 直接子 / <code>&gt; ... &gt; ...</code> 多層 / 空格 子孫</td>
      </tr>
      <tr>
          <td>過濾</td>
          <td>要排除哪些元素 / 已處理的</td>
          <td><code>:not()</code> / <code>[data-processed]</code> / WeakMap 檢查</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="起點四選一依情境">起點四選一（依情境）</h2>
<h3 id="起點-adocument-全文件-query">起點 A：Document 全文件 query</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="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></code></pre></div><p><strong>用</strong>：原型期、單例（整頁只一個）、跨元件邊界元素。
<strong>不用</strong>：production 客製、可能多實例、效能敏感（大頁面）。</p>
<h3 id="起點-b元件根變數-query">起點 B：元件根變數 query</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">root</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&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">root</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="c1">// 從 root 起
</span></span></span></code></pre></div><p><strong>用</strong>：production 客製、客製只該影響該元件、避免命中其他頁面同名元素。
<strong>不用</strong>：跨多元件邊界的 query。</p>
<h3 id="起點-c起點當函式參數">起點 C：起點當函式參數</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="kd">function</span> <span class="nx">decorate</span><span class="p">(</span><span class="nx">root</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">return</span> <span class="nx">root</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="p">}</span></span></span></code></pre></div><p><strong>用</strong>：library / utility function、需要支援多實例、純函式設計。
<strong>不用</strong>：一次性腳本（多餘的抽象）。</p>
<h3 id="起點-dclosest-反向找根">起點 D：closest() 反向找根</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="nx">button</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</span> <span class="nx">e</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">card</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">closest</span><span class="p">(</span><span class="s1">&#39;.result-card&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">card</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="s1">&#39;expanded&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p><strong>用</strong>：動態 / 多實例元件、event delegation、不知道事件源在哪一層。
<strong>不用</strong>：靜態起點已知（用 B 或 C 更直接）。</p>
<hr>
<h2 id="範圍-還是空格">範圍：<code>&gt;</code> 還是空格</h2>
<table>
  <thead>
      <tr>
          <th>寫法</th>
          <th>意思</th>
          <th>風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>.parent &gt; .child</code></td>
          <td>直接子節點</td>
          <td>安全、嚴格</td>
      </tr>
      <tr>
          <td><code>.parent .child</code></td>
          <td>任意深度子孫</td>
          <td>命中 nested 結構的同類元素</td>
      </tr>
      <tr>
          <td><code>.parent &gt; * &gt; .x</code></td>
          <td>確切兩層</td>
          <td>嚴格、結構變動時要更新</td>
      </tr>
      <tr>
          <td><code>.parent .x:not(.y)</code></td>
          <td>子孫中排除某類</td>
          <td>還是子孫範圍、:not 是過濾不是限制範圍</td>
      </tr>
  </tbody>
</table>
<p>預設 <code>&gt;</code>、有證據（多層 nested 結構都該 match）才放寬到空格。</p>
<hr>
<h2 id="過濾idempotency-標記">過濾：idempotency 標記</h2>
<p>JS 處理元素時、避免重複處理。兩種做法：</p>
<h3 id="adom-attribute-標記">A：DOM attribute 標記</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="kd">function</span> <span class="nx">decorate</span><span class="p">(</span><span class="nx">root</span><span class="p">)</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">targets</span> <span class="o">=</span> <span class="nx">root</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">&#39;.target:not([data-decorated])&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">targets</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">el</span> <span class="p">=&gt;</span> <span class="p">{</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 class="nx">el</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s1">&#39;data-decorated&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>用</strong>：production 預設、devtools 可見、跨 page reload 也保留（如果元素持久）。
<strong>不用</strong>：library 設計（不該污染使用者 DOM）。</p>
<h3 id="bweakmap-紀錄">B：WeakMap 紀錄</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">decorated</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WeakMap</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">function</span> <span class="nx">decorate</span><span class="p">(</span><span class="nx">root</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">root</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">&#39;.target&#39;</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">el</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">decorated</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="nx">el</span><span class="p">))</span> <span class="k">return</span><span class="p">;</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">decorated</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="nx">el</span><span class="p">,</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="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><strong>用</strong>：library 設計、不污染 DOM、元素 GC 後紀錄自動清。
<strong>不用</strong>：跨頁面、需要 devtools debug、需要 CSS selector 過濾（CSS 看不到 WeakMap）。</p>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1寫-css-前沒看-dom">範例 1：寫 CSS 前沒看 DOM</h3>
<blockquote>
<p>任務：把 <code>pagefind-ui__drawer</code> 排到 <code>pagefind-ui__form</code> 下方</p></blockquote>
<p><strong>錯</strong>（基於 class 命名假設）：</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</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">display</span><span class="p">:</span> <span class="k">grid</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">grid-template-rows</span><span class="p">:</span> <span class="kc">auto</span> <span class="kc">auto</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">.</span><span class="nc">pagefind-ui__form</span> <span class="p">{</span> <span class="k">grid-row</span><span class="p">:</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">.</span><span class="nc">pagefind-ui__drawer</span> <span class="p">{</span> <span class="k">grid-row</span><span class="p">:</span> <span class="mi">2</span><span class="p">;</span> <span class="p">}</span></span></span></code></pre></div><p>跑出來 drawer 跑到頁尾、grid-row 完全沒生效。</p>
<p><strong>對</strong>（先量 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 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 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 class="p">}</span>
</span></span><span class="line"><span class="ln">5</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">6</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// 返回：[DIV.pagefind-ui__drawer, FORM.pagefind-ui__form, DIV.pagefind-ui]
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1">// → drawer 是 form 的 child、不是 sibling
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="c1">// → grid-row 在 .pagefind-ui 上設、無法控制 form 的 child
</span></span></span></code></pre></div><p>→ 換方向：drawer 改 absolute、form 加 margin-bottom 留 spacer。</p>
<h3 id="範例-2selector-過寬命中無關元素">範例 2：selector 過寬命中無關元素</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="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">&#39;.title&#39;</span><span class="p">).</span><span class="nx">forEach</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">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="s1">&#39;search-title&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// 命中 page header 的 .title、navbar 的 .title、結果卡的 .title — 全變色
</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">root</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&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">root</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">&#39;:scope &gt; .results &gt; .result &gt; .title&#39;</span><span class="p">).</span><span class="nx">forEach</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">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="s1">&#39;search-title&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// 起點 = .pagefind-ui、範圍 = 確切三層、過濾 = 不需要（已精準）
</span></span></span></code></pre></div><hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>寫 CSS 規則或 JS query 前：</p>
<ul>
<li><input disabled="" type="checkbox"> 我有沒有量過真實 DOM tree（playwright <code>browser_evaluate</code> 或 DevTools）？</li>
<li><input disabled="" type="checkbox"> Selector 的「起點」明確嗎？是 document / 元件根 / 函式參數 / closest 哪一個？</li>
<li><input disabled="" type="checkbox"> Selector 的「範圍」明確嗎？是 <code>&gt;</code> 直接子還是空格子孫？</li>
<li><input disabled="" type="checkbox"> Selector 的「過濾」明確嗎？需要 idempotency 標記嗎？</li>
<li><input disabled="" type="checkbox"> 過寬的 selector（<code>document.querySelectorAll('*')</code>、<code>[class*=&quot;x&quot;]</code>）能不能換成更精準的？</li>
</ul>
<p>任一項打勾失敗 → 補上、再寫規則。</p>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code>）：</p>
<ul>
<li><a href="/blog/report/dom-topology-before-css/" data-link-title="拓樸理解先行於 CSS 規則" data-link-desc="寫 CSS 之前看真實 DOM tree、不靠 class name 推測層級。本文以『drawer 在 form 內、不是 form 的 sibling』這個假設錯誤為例，展開『拓樸理解 → CSS 規則』的順序。">dom-topology-before-css</a> — 拓樸理解先行於 CSS 規則</li>
<li><a href="/blog/report/dom-selector-precision/" data-link-title="Selector 精準度：讓 query 只命中你想要的元素" data-link-desc="JS 的 DOM query 是 sanity 防線、不是優化選項。從『起點 / 範圍 / 過濾』三層收斂、避免誤命中、避免未來頁面結構變動讓 query 撈到不該撈的東西。本文是 selector 設計的完整指引。">dom-selector-precision</a> — Selector 精準度三維度</li>
<li><a href="/blog/report/pattern-document-query/" data-link-title="Pattern：Document 全文件 query" data-link-desc="`document.querySelector` 從整個頁面找元素 — 是探索期與一次性 script 的合理工具、不是 production 客製的預設。本文展開這個 pattern 的適用邊界。">pattern-document-query</a> / <a href="/blog/report/pattern-component-root/" data-link-title="Pattern：元件根變數 query" data-link-desc="把元件根 `var shell = document.querySelector(&#39;.shell&#39;)` 一次存變數、之後所有 query 從 shell 開始 — 是 production 客製的預設起點。本文展開這個 pattern 的設計細節與邊界。">pattern-component-root</a> / <a href="/blog/report/pattern-root-as-parameter/" data-link-title="Pattern：起點當函式參數" data-link-desc="把元件根當函式參數傳入 — `function setup(shell) { shell.querySelector(...) }`、外部呼叫 `forEach(setup)` 處理多實例。本文展開純函式設計與多實例支援的取捨。">pattern-root-as-parameter</a> / <a href="/blog/report/pattern-closest-lookup/" data-link-title="Pattern：closest 反向找根" data-link-desc="事件處理時用 `e.target.closest(&#39;.shell&#39;)` 從事件目標反向找元件根 — 適合動態元件、SPA 路由切換、事件委派場景。本文展開反向定位 pattern 的應用邊界。">pattern-closest-lookup</a> — 起點四選一 pattern 卡片</li>
<li><a href="/blog/report/pattern-attribute-idempotency-marker/" data-link-title="Pattern：DOM attribute idempotency 標記" data-link-desc="用 `:not([data-x])` 過濾 &#43; 處理後 `setAttribute(&#39;data-x&#39;, &#39;true&#39;)` 保證每元素只處理一次 — 是 production apply 函式的預設 idempotency 工具。本文展開命名、生命週期、跟 framework 共處的設計細節。">pattern-attribute-idempotency-marker</a> / <a href="/blog/report/pattern-weakmap-idempotency-record/" data-link-title="Pattern：WeakMap idempotency 紀錄" data-link-desc="用 `WeakMap` 紀錄已處理的元素 — 不污染 DOM、適合第三方 library、跟 framework 衝突場景。本文展開 GC 行為、debug 替代方案、跟 attribute 標記的取捨。">pattern-weakmap-idempotency-record</a> — Idempotency 兩選一</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.1.0</p>
]]></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>Framework Coexistence — 跟 framework-managed DOM 共處</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/framework-coexistence/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/framework-coexistence/</guid><description>&lt;p>跟 framework-managed DOM 共處：把 framework 子樹當禁區、客製 UI 注入到 boundary 外、JS 操作邊界由穩定性梯度決定、外部組件客製優先用公共介面。&lt;/p>
&lt;p>適用：跟 vendor library / framework component（pagefind、Vue widget、React component、jQuery plugin）共存的客製、注入客製 UI、覆寫 vendor 樣式。
不適用：完全自家寫的元件（沒有 framework 介入）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋 framework 邊界辨識、JS 操作的四級安全度、外部組件客製的四層合作。&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>客製 UI 注入到 framework 子樹後被還原&lt;/td>
 &lt;td>移到 framework 邊界外、用 CSS 控制視覺位置&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Vendor library 升級後客製樣式失效&lt;/td>
 &lt;td>改用公共介面（CSS var / API）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>不確定 reparent / 改 attribute / 改 textContent 哪個安全&lt;/td>
 &lt;td>看下方「JS 操作四級安全度」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>客製需求看似簡單但要動 framework 內部結構&lt;/td>
 &lt;td>評估「值不值得」、把成本攤開（見 cost report）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>寫了 MutationObserver 補 framework reconcile 後元素還原&lt;/td>
 &lt;td>換思路：注入到邊界外、不需要 observer&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼-framework-managed-dom-要當禁區">為什麼 framework-managed DOM 要當禁區&lt;/h2>
&lt;p>Framework（React / Vue / vendor JS widget）對它管的 DOM 子樹有&lt;strong>所有權&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>State 變動觸發 reconciliation、子樹重建&lt;/li>
&lt;li>我們改的 attribute / textContent / 子節點被還原&lt;/li>
&lt;li>innerHTML 改動可能觸發 Vue / React 的 dev mode 警告&lt;/li>
&lt;li>Event listener 失效（節點被替換）&lt;/li>
&lt;/ul>
&lt;p>把客製 UI &lt;strong>注入到 framework 邊界外&lt;/strong>（vendor root 的 sibling、或上一層 container 的另一個 child）→ framework 不管它 → 不會被還原。&lt;/p>
&lt;hr>
&lt;h2 id="framework-邊界的識別">Framework 邊界的識別&lt;/h2>
&lt;h3 id="邊界的可見訊號">邊界的可見訊號&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>訊號&lt;/th>
 &lt;th>含義&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>&amp;lt;div id=&amp;quot;app&amp;quot;&amp;gt;&lt;/code> / &lt;code>data-vue-component&lt;/code>&lt;/td>
 &lt;td>Vue / 自家 framework 的 root&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>&amp;lt;div data-reactroot&amp;gt;&lt;/code> / React Fiber 結構&lt;/td>
 &lt;td>React 的 root&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>.pagefind-ui&lt;/code> / &lt;code>.algolia-search&lt;/code> 等命名空間&lt;/td>
 &lt;td>Vendor library 的 root&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>子節點 attribute 含 &lt;code>__data&lt;/code> &lt;code>__key&lt;/code> 等內部標記&lt;/td>
 &lt;td>Framework 內部結構、子節點被管理&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>邊界外的 sibling / parent 通常是「自家 HTML」、安全。&lt;/p>
&lt;h3 id="範例pagefind-的邊界">範例：Pagefind 的邊界&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">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;search-page&amp;#34;&lt;/span>&lt;span class="p">&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">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Search&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&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">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;custom-filter&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> ← 自家、客製 UI 放這裡
&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">div&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">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;pagefind-ui&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> ← Vendor root (邊界、入內就是禁區)
&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">form&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;pagefind-ui__form&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> ← Pagefind 管
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">input&lt;/span> &lt;span class="err">...&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&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;pagefind-ui__drawer&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> ← Pagefind 管（重渲染時清空）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&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;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">form&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&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;span class="line">&lt;span class="ln">12&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>&lt;code>.custom-filter&lt;/code> 跟 &lt;code>.pagefind-ui&lt;/code> 是 sibling、不在 vendor 子樹內 → 用 CSS grid / absolute 定位讓它看起來在 search 流程內、但實際 framework 不管它。&lt;/p></description><content:encoded><![CDATA[<p>跟 framework-managed DOM 共處：把 framework 子樹當禁區、客製 UI 注入到 boundary 外、JS 操作邊界由穩定性梯度決定、外部組件客製優先用公共介面。</p>
<p>適用：跟 vendor library / framework component（pagefind、Vue widget、React component、jQuery plugin）共存的客製、注入客製 UI、覆寫 vendor 樣式。
不適用：完全自家寫的元件（沒有 framework 介入）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋 framework 邊界辨識、JS 操作的四級安全度、外部組件客製的四層合作。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>客製 UI 注入到 framework 子樹後被還原</td>
          <td>移到 framework 邊界外、用 CSS 控制視覺位置</td>
      </tr>
      <tr>
          <td>Vendor library 升級後客製樣式失效</td>
          <td>改用公共介面（CSS var / API）</td>
      </tr>
      <tr>
          <td>不確定 reparent / 改 attribute / 改 textContent 哪個安全</td>
          <td>看下方「JS 操作四級安全度」</td>
      </tr>
      <tr>
          <td>客製需求看似簡單但要動 framework 內部結構</td>
          <td>評估「值不值得」、把成本攤開（見 cost report）</td>
      </tr>
      <tr>
          <td>寫了 MutationObserver 補 framework reconcile 後元素還原</td>
          <td>換思路：注入到邊界外、不需要 observer</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼-framework-managed-dom-要當禁區">為什麼 framework-managed DOM 要當禁區</h2>
<p>Framework（React / Vue / vendor JS widget）對它管的 DOM 子樹有<strong>所有權</strong>：</p>
<ul>
<li>State 變動觸發 reconciliation、子樹重建</li>
<li>我們改的 attribute / textContent / 子節點被還原</li>
<li>innerHTML 改動可能觸發 Vue / React 的 dev mode 警告</li>
<li>Event listener 失效（節點被替換）</li>
</ul>
<p>把客製 UI <strong>注入到 framework 邊界外</strong>（vendor root 的 sibling、或上一層 container 的另一個 child）→ framework 不管它 → 不會被還原。</p>
<hr>
<h2 id="framework-邊界的識別">Framework 邊界的識別</h2>
<h3 id="邊界的可見訊號">邊界的可見訊號</h3>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>含義</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>&lt;div id=&quot;app&quot;&gt;</code> / <code>data-vue-component</code></td>
          <td>Vue / 自家 framework 的 root</td>
      </tr>
      <tr>
          <td><code>&lt;div data-reactroot&gt;</code> / React Fiber 結構</td>
          <td>React 的 root</td>
      </tr>
      <tr>
          <td><code>.pagefind-ui</code> / <code>.algolia-search</code> 等命名空間</td>
          <td>Vendor library 的 root</td>
      </tr>
      <tr>
          <td>子節點 attribute 含 <code>__data</code> <code>__key</code> 等內部標記</td>
          <td>Framework 內部結構、子節點被管理</td>
      </tr>
  </tbody>
</table>
<p>邊界外的 sibling / parent 通常是「自家 HTML」、安全。</p>
<h3 id="範例pagefind-的邊界">範例：Pagefind 的邊界</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">class</span><span class="o">=</span><span class="s">&#34;search-page&#34;</span><span class="p">&gt;</span>           ← 自家 (邊界外、可控)
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Search<span class="p">&lt;/</span><span class="nt">h1</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">class</span><span class="o">=</span><span class="s">&#34;custom-filter&#34;</span><span class="p">&gt;</span>       ← 自家、客製 UI 放這裡
</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="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">class</span><span class="o">=</span><span class="s">&#34;pagefind-ui&#34;</span><span class="p">&gt;</span>          ← Vendor root (邊界、入內就是禁區)
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">&lt;</span><span class="nt">form</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;pagefind-ui__form&#34;</span><span class="p">&gt;</span> ← Pagefind 管
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      <span class="p">&lt;</span><span class="nt">input</span> <span class="err">...</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="na">class</span><span class="o">=</span><span class="s">&#34;pagefind-ui__drawer&#34;</span><span class="p">&gt;</span>  ← Pagefind 管（重渲染時清空）
</span></span><span class="line"><span class="ln"> 9</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">10</span><span class="cl">    <span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">11</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">12</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><code>.custom-filter</code> 跟 <code>.pagefind-ui</code> 是 sibling、不在 vendor 子樹內 → 用 CSS grid / absolute 定位讓它看起來在 search 流程內、但實際 framework 不管它。</p>
<hr>
<h2 id="js-操作的四級安全度">JS 操作的四級安全度</h2>
<p>對 framework-managed 元素的操作、按穩定性排序：</p>
<table>
  <thead>
      <tr>
          <th>操作</th>
          <th>安全度</th>
          <th>為什麼</th>
          <th>補救</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Reparent 整節點</td>
          <td>高</td>
          <td>整節點搬遷、framework 通常不會還原</td>
          <td>-</td>
      </tr>
      <tr>
          <td>改 inline style</td>
          <td>中-高</td>
          <td>Style 通常不被 reconcile（除非 framework 重設）</td>
          <td>用 CSS class 取代</td>
      </tr>
      <tr>
          <td>改 attribute</td>
          <td>中</td>
          <td>部分 framework 會 reconcile attribute</td>
          <td>用 MutationObserver 補回</td>
      </tr>
      <tr>
          <td>改 textContent</td>
          <td>中-低</td>
          <td>多數 framework 會 reconcile text</td>
          <td>改注入新節點到邊界外</td>
      </tr>
      <tr>
          <td>改 innerHTML</td>
          <td>低</td>
          <td>子節點全重建、event listener 失效</td>
          <td>不要改、用其他方法</td>
      </tr>
      <tr>
          <td>改 framework 子節點</td>
          <td>極低</td>
          <td>reconcile 還原、可能 dev warning</td>
          <td>不要動</td>
      </tr>
  </tbody>
</table>
<p>選擇規則：<strong>從最高安全度起步、不行才升級</strong>。</p>
<hr>
<h2 id="客製-ui-注入的兩種模式">客製 UI 注入的兩種模式</h2>
<h3 id="模式-1注入到-framework-邊界外推薦">模式 1：注入到 framework 邊界外（推薦）</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">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">2</span><span class="cl"><span class="nx">customEl</span><span class="p">.</span><span class="nx">className</span> <span class="o">=</span> <span class="s1">&#39;custom-filter&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</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;Filter: All / Title / Content&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.search-page&#39;</span><span class="p">).</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">customEl</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// 注意：appendChild 到 .search-page、不是 .pagefind-ui
</span></span></span></code></pre></div>




<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">search-page</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="k">display</span><span class="p">:</span> <span class="k">grid</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="k">grid-template</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;h1&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;form&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;custom-filter&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;results&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">.</span><span class="nc">pagefind-ui</span> <span class="p">{</span> <span class="k">grid-area</span><span class="p">:</span> <span class="n">form</span> <span class="o">/</span> <span class="n">form</span> <span class="o">/</span> <span class="n">results</span> <span class="o">/</span> <span class="n">results</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">.</span><span class="nc">custom-filter</span> <span class="p">{</span> <span class="k">grid-area</span><span class="p">:</span> <span class="n">custom-filter</span><span class="p">;</span> <span class="p">}</span></span></span></code></pre></div><p>CSS grid 把客製 UI 排到 search 流程的某個位置、framework 不知情、不還原。</p>
<h3 id="模式-2reparent-framework-內節點次優">模式 2：reparent framework 內節點（次優）</h3>
<p>如果客製需要把 framework 內的某個元素移到別處 — reparent 整節點而不是改內部：</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">filter</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__filters&#39;</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">target</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;.sidebar&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">target</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>  <span class="c1">// 整節點搬到 sidebar、不複製
</span></span></span></code></pre></div><p>整節點搬遷通常 framework 不會「還原」、因為 vDOM diff 看到 node 還在（只是 parent 變了）。</p>
<p>但有 case 例外（部分 framework 用 portal pattern、reparent 會被視為 unmount）→ 第 1 次嘗試後用 playwright 驗證行為、第 2 次失敗就停（見 requirement-protocol/failure-pivot-protocol）。</p>
<hr>
<h2 id="外部組件客製的四層合作穩定性梯度">外部組件客製的四層合作（穩定性梯度）</h2>
<p>跟外部組件合作時、選哪一層客製、決定升級時會不會壞。</p>
<table>
  <thead>
      <tr>
          <th>層</th>
          <th>範例</th>
          <th>升級穩定性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>公共介面層</td>
          <td>CLI 參數、CSS variable、option 物件</td>
          <td>最高</td>
      </tr>
      <tr>
          <td>邊界層</td>
          <td>注入 root 的 sibling、用 CSS 包邊界外</td>
          <td>高</td>
      </tr>
      <tr>
          <td>邊界 DOM 層</td>
          <td>querySelector vendor 的 root 節點</td>
          <td>中</td>
      </tr>
      <tr>
          <td>內部結構層</td>
          <td>改 vendor 子節點 attribute / 樣式</td>
          <td>最低</td>
      </tr>
  </tbody>
</table>
<p><strong>選擇順序</strong>：先看公共介面有沒有提供（讀 docs）、沒有再用邊界層、再不行才碰邊界 DOM。內部結構層幾乎不要碰 — 升級時 minor version 都會壞。</p>
<h3 id="範例pagefind-的客製優先順序">範例：Pagefind 的客製優先順序</h3>
<table>
  <thead>
      <tr>
          <th>需求</th>
          <th>優先做法</th>
          <th>次選</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>改主題色</td>
          <td>公共：<code>--pagefind-ui-primary</code> CSS var</td>
          <td>邊界 DOM：覆寫 <code>.pagefind-ui__form</code></td>
      </tr>
      <tr>
          <td>加 filter UI</td>
          <td>邊界：在 <code>.pagefind-ui</code> sibling 注入</td>
          <td>內部：塞進 <code>.pagefind-ui__form</code> 內</td>
      </tr>
      <tr>
          <td>限定 search scope</td>
          <td>公共：<code>pagefindOptions.scope: 'main'</code></td>
          <td>內部：MutationObserver 過濾結果</td>
      </tr>
      <tr>
          <td>改 result 卡片排版</td>
          <td>邊界 DOM：覆寫 <code>.pagefind-ui__result</code> CSS（接受升級時可能要重檢）</td>
          <td>-</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1客製-filter-ui">範例 1：客製 filter UI</h3>
<p><strong>錯</strong>（注入到 vendor 子樹內）：</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">filter</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">2</span><span class="cl"><span class="nx">filter</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="s1">&#39;Filter: ...&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.pagefind-ui__form&#39;</span><span class="p">).</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// → search 觸發 → form 重渲染 → filter 消失
</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">filter</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">2</span><span class="cl"><span class="nx">filter</span><span class="p">.</span><span class="nx">className</span> <span class="o">=</span> <span class="s1">&#39;custom-filter&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">filter</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="s1">&#39;Filter: ...&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.search-page&#39;</span><span class="p">).</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span></span></span></code></pre></div>




<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">search-page</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-areas</span><span class="p">:</span> <span class="s2">&#34;h1&#34;</span> <span class="s2">&#34;form&#34;</span> <span class="s2">&#34;filter&#34;</span> <span class="s2">&#34;results&#34;</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</span> <span class="p">{</span> <span class="k">grid-area</span><span class="p">:</span> <span class="n">form</span> <span class="o">/</span> <span class="n">form</span> <span class="o">/</span> <span class="n">results</span> <span class="o">/</span> <span class="n">results</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">custom-filter</span> <span class="p">{</span> <span class="k">grid-area</span><span class="p">:</span> <span class="k">filter</span><span class="p">;</span> <span class="p">}</span></span></span></code></pre></div><h3 id="範例-2改-vendor-主題色">範例 2：改 vendor 主題色</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="p">.</span><span class="nc">pagefind-ui__form</span> <span class="p">{</span> <span class="k">background</span><span class="p">:</span> <span class="kc">blue</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">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="kc">darkblue</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>升級後 class 改名 → 全壞。</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="p">:</span><span class="nd">root</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nv">--pagefind-ui-primary</span><span class="p">:</span> <span class="mh">#2c5282</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nv">--pagefind-ui-text</span><span class="p">:</span> <span class="mh">#fff</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nv">--pagefind-ui-background</span><span class="p">:</span> <span class="mh">#1a202c</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><p>讀 vendor docs、用提供的 CSS var。升級安全、5 行解決。</p>
<h3 id="範例-3把-vendor-filter-移到-sidebar">範例 3：把 vendor filter 移到 sidebar</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="kr">const</span> <span class="nx">original</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__filters&#39;</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">clone</span> <span class="o">=</span> <span class="nx">original</span><span class="p">.</span><span class="nx">cloneNode</span><span class="p">(</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="nx">sidebar</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">clone</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// → 兩份、state 不同步、click 事件 listener 沒複製
</span></span></span></code></pre></div><p><strong>對</strong>（reparent 整節點）：</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">filter</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__filters&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">sidebar</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>  <span class="c1">// 整節點搬遷、event listener 跟著、state 唯一
</span></span></span></code></pre></div><p>整節點搬遷通常安全 — vDOM 看到 node 還在、不會 reconcile。寫完先用 playwright 驗證行為（dispatch input / click 看 filter 是否還工作）。</p>
<hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>跟 framework / vendor library 共處時：</p>
<ul>
<li><input disabled="" type="checkbox"> 我有沒有先看 vendor docs、確認有沒有公共介面（CSS var / API）？</li>
<li><input disabled="" type="checkbox"> 客製 UI 是注入到 framework 邊界外、還是內部？</li>
<li><input disabled="" type="checkbox"> JS 操作的元素是 framework 管的子節點嗎？如果是、有沒有用「四級安全度」最高的操作？</li>
<li><input disabled="" type="checkbox"> reparent / 改 attribute 後、有沒有用 playwright 驗證 framework 沒還原？</li>
<li><input disabled="" type="checkbox"> 升級風險有攤給使用者嗎？（見 requirement-protocol/cost-and-checkpoint）</li>
</ul>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code>）：</p>
<ul>
<li><a href="/blog/report/coexisting-with-framework-managed-dom/" data-link-title="客製 UI 留 framework 邊界外、用 CSS 控制視覺位置" data-link-desc="Svelte / React 等框架對自己管轄的 DOM 子樹有完整渲染週期 — 客製 UI 注入這個子樹會被框架重繪清掉。本文展開「邊界外 &#43; CSS 視覺定位」這條策略：為什麼 framework 會清外來節點、CSS 怎麼達到注入想要的視覺效果、什麼時候這條策略不適用。">coexisting-with-framework-managed-dom</a> — 客製 UI 留 framework 邊界外、用 CSS 控制視覺位置</li>
<li><a href="/blog/report/component-boundary-and-js-impact/" data-link-title="JS 操作 framework 元件：邊界辨識與安全規則" data-link-desc="操作 framework 管的 DOM 前先界定『動什麼、邊界在哪、state 由誰維護』。本文展開邊界辨識的決策樹、整節點 reparent 的具體 do/don&#39;t 規則、灰區操作的 fail-safe 設計。">component-boundary-and-js-impact</a> — JS 操作 framework 元件的邊界辨識</li>
<li><a href="/blog/report/external-component-customization/" data-link-title="在外部組件上加客製功能：以邊界為中心的方法選擇" data-link-desc="客製外部組件穩定的程度取決於『離組件邊界多近』。本文用 Pagefind 整合到 Hugo theme 的三個情境（索引邊界、重置邊界、specificity 邊界）展開：在邊界上客製為什麼穩、各種替代方案的不足、以及下次提早辨識的訊號。">external-component-customization</a> — 在外部組件上加客製功能：以邊界為中心</li>
<li><a href="/blog/report/external-component-collaboration-layers/" data-link-title="跟外部組件合作的層次：離介面越近、合作越穩" data-link-desc="客製外部組件的穩定性與「離組件作者保證的對外介面多遠」成反比。每往內推一層、依賴前提增加、升級風險上升、可逆性下降。本文是 #1 / #5 / #19 / #24 四篇實作的共同抽象。">external-component-collaboration-layers</a> — 跟外部組件合作的四層次</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.1.0</p>
]]></content:encoded></item><item><title>Frontend with Playwright — SKILL 入口</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/skill/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/skill/</guid><description>&lt;p>框架無關的前端開發協議 + Playwright 驗證。原則適用於 vanilla HTML/CSS/JS、Vue、React、jQuery — 因為核心是「DOM / CSS / JS 三者的本質行為」加上「Playwright 用 live DOM 量測驗證」、不依賴特定框架的渲染機制。&lt;/p>
&lt;p>協議的核心命題：&lt;strong>先讀真實狀態、再寫規則；先量再改、不要靠假設&lt;/strong>。前端 bug 多半來自「寫 CSS 時假設的 DOM 結構與實際不符」、「JS 改完元素被 framework 還原」、「listener 觸發頻率失控」。Playwright 把這些假設變成可驗證的量測值。&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>Read Before Write&lt;/strong> 先讀真實狀態&lt;/td>
 &lt;td>寫 CSS 前用 playwright/DevTools 量真實 DOM；寫 JS 前確認 framework 邊界&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>CSS-First, JS-Augment&lt;/strong> CSS 為主、JS 補強&lt;/td>
 &lt;td>能 build-time 算的進 CSS、必須 runtime 量測的進 JS、邊界清楚不混搭&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Measure, Don&amp;rsquo;t Assume&lt;/strong> 量測、不要假設&lt;/td>
 &lt;td>Layout / 行為 / 互動三層、用 playwright &lt;code>browser_evaluate&lt;/code> 把假設變已知&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-寫-css-前先確認-dom-topology">1. 寫 CSS 前先確認 DOM topology&lt;/h3>
&lt;p>Class name 是約定、不是結構保證。寫 CSS 規則之前、用 playwright &lt;code>browser_evaluate&lt;/code> 讀目標元素的 ancestor chain — 確認它在 DOM tree 的哪個位置、parent / sibling / 共用的 grid cell 是什麼。&lt;/p>
&lt;p>Selector 設計三維度：&lt;strong>起點（document / 元件根 / 函式參數 / closest）+ 範圍（直接子節點 / 子孫）+ 過濾（attribute / 已處理標記）&lt;/strong>。預設用最精準的、有證據再放寬。&lt;/p>
&lt;h3 id="2-css--js-的邊界由值能否-build-time-定下來決定">2. CSS / JS 的邊界由「值能否 build-time 定下來」決定&lt;/h3>
&lt;p>能在 build time 算出來的值（design token、固定 breakpoint、靜態尺寸）→ 寫進 CSS variable / static rule。&lt;strong>必須 runtime 才能知道的值&lt;/strong>（form 高度、scroll 位置、container 寬度）→ JS 量測後寫回 CSS variable、CSS 仍然只讀變數。&lt;/p>
&lt;p>JS 的職責是 &lt;strong>toggle class / 寫 var&lt;/strong>、不是設 inline style。&lt;code>!important&lt;/code> / inline &lt;code>display: none&lt;/code> 是 anti-pattern — 改用 class toggle 把樣式留在 CSS。Vendor CSS 用 &lt;code>@layer&lt;/code> 包起來、自家 unlayered 自動贏 specificity。&lt;/p>
&lt;h3 id="3-playwright-在開發循環的三個位置">3. Playwright 在開發循環的三個位置&lt;/h3>
&lt;p>&lt;strong>位置 1：假設驗證&lt;/strong>（寫 CSS 前）— 讀 ancestor chain、確認結構符合假設。
&lt;strong>位置 2：行為驗證&lt;/strong>（規則寫完後）— 讀 bounding rect / computed style、確認 layout 結果。
&lt;strong>位置 3：互動驗證&lt;/strong>（dispatch event 後讀 state）— 模擬 input / click、量化驗證互動結果。&lt;/p></description><content:encoded><![CDATA[<p>框架無關的前端開發協議 + Playwright 驗證。原則適用於 vanilla HTML/CSS/JS、Vue、React、jQuery — 因為核心是「DOM / CSS / JS 三者的本質行為」加上「Playwright 用 live DOM 量測驗證」、不依賴特定框架的渲染機制。</p>
<p>協議的核心命題：<strong>先讀真實狀態、再寫規則；先量再改、不要靠假設</strong>。前端 bug 多半來自「寫 CSS 時假設的 DOM 結構與實際不符」、「JS 改完元素被 framework 還原」、「listener 觸發頻率失控」。Playwright 把這些假設變成可驗證的量測值。</p>
<hr>
<h2 id="core-pillars支柱">Core Pillars（支柱）</h2>
<table>
  <thead>
      <tr>
          <th>支柱</th>
          <th>意義</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Read Before Write</strong> 先讀真實狀態</td>
          <td>寫 CSS 前用 playwright/DevTools 量真實 DOM；寫 JS 前確認 framework 邊界</td>
      </tr>
      <tr>
          <td><strong>CSS-First, JS-Augment</strong> CSS 為主、JS 補強</td>
          <td>能 build-time 算的進 CSS、必須 runtime 量測的進 JS、邊界清楚不混搭</td>
      </tr>
      <tr>
          <td><strong>Measure, Don&rsquo;t Assume</strong> 量測、不要假設</td>
          <td>Layout / 行為 / 互動三層、用 playwright <code>browser_evaluate</code> 把假設變已知</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="principles原則速查">Principles（原則速查）</h2>
<p>讀者在本區塊能完成大方向判斷；具體展開（步驟 / 範例）依下方「觸發路由」進對應 reference。</p>
<h3 id="1-寫-css-前先確認-dom-topology">1. 寫 CSS 前先確認 DOM topology</h3>
<p>Class name 是約定、不是結構保證。寫 CSS 規則之前、用 playwright <code>browser_evaluate</code> 讀目標元素的 ancestor chain — 確認它在 DOM tree 的哪個位置、parent / sibling / 共用的 grid cell 是什麼。</p>
<p>Selector 設計三維度：<strong>起點（document / 元件根 / 函式參數 / closest）+ 範圍（直接子節點 / 子孫）+ 過濾（attribute / 已處理標記）</strong>。預設用最精準的、有證據再放寬。</p>
<h3 id="2-css--js-的邊界由值能否-build-time-定下來決定">2. CSS / JS 的邊界由「值能否 build-time 定下來」決定</h3>
<p>能在 build time 算出來的值（design token、固定 breakpoint、靜態尺寸）→ 寫進 CSS variable / static rule。<strong>必須 runtime 才能知道的值</strong>（form 高度、scroll 位置、container 寬度）→ JS 量測後寫回 CSS variable、CSS 仍然只讀變數。</p>
<p>JS 的職責是 <strong>toggle class / 寫 var</strong>、不是設 inline style。<code>!important</code> / inline <code>display: none</code> 是 anti-pattern — 改用 class toggle 把樣式留在 CSS。Vendor CSS 用 <code>@layer</code> 包起來、自家 unlayered 自動贏 specificity。</p>
<h3 id="3-playwright-在開發循環的三個位置">3. Playwright 在開發循環的三個位置</h3>
<p><strong>位置 1：假設驗證</strong>（寫 CSS 前）— 讀 ancestor chain、確認結構符合假設。
<strong>位置 2：行為驗證</strong>（規則寫完後）— 讀 bounding rect / computed style、確認 layout 結果。
<strong>位置 3：互動驗證</strong>（dispatch event 後讀 state）— 模擬 input / click、量化驗證互動結果。</p>
<p>第 2 次同個版型 bug → 把 query 寫成 playwright 測試固化、CI 防回歸。</p>
<h3 id="4-與-framework-managed-dom-共處的邊界辨識">4. 與 framework-managed DOM 共處的邊界辨識</h3>
<p>把 framework 子樹當「禁區」、客製 UI 注入到 framework 邊界外、用 CSS 控制視覺位置（absolute / margin / grid）。框架重渲染時、邊界外的客製 UI 不被 reconcile 清掉。</p>
<p><strong>JS 操作的邊界穩定性</strong>（從穩到不穩）：reparent 整節點 &gt; 改 inline style &gt; 改 attribute &gt; 改 textContent &gt; 改 innerHTML &gt; 改 framework 子節點。穩定性低的需要 MutationObserver 重做、或乾脆別碰。</p>
<p><strong>外部組件客製的合作層次</strong>（穩定性梯度）：CSS variable / API &gt; class hook &gt; boundary DOM &gt; 內部結構。離公共介面越近、升級越穩。</p>
<h3 id="5-reactive-監聽器的頻率盤點">5. Reactive 監聽器的頻率盤點</h3>
<p>MutationObserver 三維度：<strong>root（最窄）、options（最少）、debounce（最長可接受）</strong>。預設 <code>observer.observe(scope, { childList: true })</code>、不寫 <code>subtree: true</code> 除非有 case。</p>
<p>Polling（<code>setTimeout</code> / <code>setInterval</code>）有事件可監聽就替換成 MutationObserver — 0 latency / 0 idle CPU。Reactive perf debug 從 <code>console.count(callbackName)</code> 起、確認觸發頻率符合預期。</p>
<p>效能風險點四面向：<strong>iteration 成本（500 results × regex test）、reflow 成本（&gt;16ms 觸發 jank）、listener 頻率（如上）、resource 載入時序（lazy chunk vs critical path）</strong>。</p>
<h3 id="6-a11y-三道防線">6. A11y 三道防線</h3>
<p><strong>鍵盤可達性</strong>：visible focus indicator、邏輯 tab 順序、modal 有 escape 路徑。三者缺一不可。
<strong>動態 a11y</strong>：JS reparent / hide 時保存並還原 focus；變動內容用 <code>aria-live=&quot;polite&quot;</code> 廣播給 screen reader。
<strong>Native &gt; ARIA</strong>：能用 <code>&lt;button&gt;</code> / <code>&lt;fieldset&gt;</code> / <code>&lt;dialog&gt;</code> 就不要自己組 ARIA role — native HTML 自帶 keyboard / focus / a11y tree、ARIA 是補強不是替代。</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>要寫 CSS 規則、需要先確認 DOM 結構 / selector 該怎麼寫</td>
          <td><code>references/dom-topology-first.md</code></td>
      </tr>
      <tr>
          <td>不確定 selector 該多寬、命中其他元素</td>
          <td><code>references/dom-topology-first.md</code></td>
      </tr>
      <tr>
          <td>不確定值該寫進 CSS 還是 JS、CSS layers / variable / class toggle 取捨</td>
          <td><code>references/css-js-boundary.md</code></td>
      </tr>
      <tr>
          <td>用 <code>!important</code> / inline style 解 specificity</td>
          <td><code>references/css-js-boundary.md</code></td>
      </tr>
      <tr>
          <td>要用 playwright 驗證 layout / 假設 / 互動</td>
          <td><code>references/playwright-in-loop.md</code></td>
      </tr>
      <tr>
          <td>Layout bug 第 2 次出現、想寫成測試</td>
          <td><code>references/playwright-in-loop.md</code></td>
      </tr>
      <tr>
          <td>客製 UI 被 framework 還原、不知道該注入到哪</td>
          <td><code>references/framework-coexistence.md</code></td>
      </tr>
      <tr>
          <td>要客製外部組件（pagefind / vendor library）</td>
          <td><code>references/framework-coexistence.md</code></td>
      </tr>
      <tr>
          <td>使用者反映卡頓、CPU 100%、scroll lag、resize jank</td>
          <td><code>references/reactive-performance.md</code></td>
      </tr>
      <tr>
          <td>要設計 MutationObserver / event listener 範圍</td>
          <td><code>references/reactive-performance.md</code></td>
      </tr>
      <tr>
          <td>要驗收鍵盤 / screen reader / motor / 視覺 a11y</td>
          <td><code>references/accessibility-and-focus.md</code></td>
      </tr>
      <tr>
          <td>JS reparent 後 focus 跑掉、aria-live 沒朗讀</td>
          <td><code>references/accessibility-and-focus.md</code></td>
      </tr>
      <tr>
          <td>設計 filter / sort / count 操作、source 是分批 / streaming</td>
          <td><code>references/data-flow-and-filter-composition.md</code></td>
      </tr>
      <tr>
          <td>「Load more 後畫面閃但內容沒變」的 silent 缺口</td>
          <td><code>references/data-flow-and-filter-composition.md</code>（層錯位）</td>
      </tr>
      <tr>
          <td>Backend / 演算法 / map-reduce 的 post-filter 漏項</td>
          <td><code>references/data-flow-and-filter-composition.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">frontend-with-playwright/
</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">    ├── dom-topology-first.md                   # 情境 1：寫 CSS 前用 playwright/DevTools 量真實 DOM、selector 設計
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    ├── css-js-boundary.md                      # 情境 2：CSS-only vs JS-assisted、class toggle、layers、variable 單一位置、檔案拆分
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    ├── playwright-in-loop.md                   # 情境 3：playwright 三個位置（假設 / 行為 / 互動驗證）+ 寫成 layout test
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    ├── framework-coexistence.md                # 情境 4：custom UI 留 framework 邊界外、外部組件四層合作、JS 操作邊界辨識
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    ├── reactive-performance.md                 # 情境 5：observer scope、polling→observer、頻率盤點、iteration / regex / reflow
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    ├── accessibility-and-focus.md              # 情境 6：focus on DOM move、keyboard 三要素、aria-live、native HTML &gt; ARIA
</span></span><span class="line"><span class="ln">10</span><span class="cl">    └── data-flow-and-filter-composition.md     # 情境 7：Filter × Source 層錯位 + 五策略 + 跨領域（前端 / 後端 / 演算法 / DB）</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="跟-requirement-protocol-的關係">跟 requirement-protocol 的關係</h2>
<p><code>requirement-protocol</code> 是上層的「對話協議」（澄清需求、失敗轉折、覆寫成本、工具切換時機）；本 skill 是下層的「前端執行協議」（DOM / CSS / JS / Playwright 的具體做法）。</p>
<p>當情境是「不確定該怎麼跟使用者溝通」 → 讀 requirement-protocol。
當情境是「知道要做什麼、不確定前端該怎麼實作驗證」 → 讀本 skill。
兩個 skill 的 <code>playwright</code> 段落互補：<code>requirement-protocol/tool-switching-timing</code> 講「何時切」、本 skill 的 <code>playwright-in-loop</code> 講「切了之後具體寫什麼 query」。</p>
<p><code>requirement-protocol/clarifying-ambiguous-instructions</code> 的「類型 5：篩選類」跟本 skill 的 <code>data-flow-and-filter-composition</code> 互補：上層講「該怎麼澄清」、本層講「澄清完該怎麼實作」。</p>
<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 次是訊號（playwright 切換時機的根據）</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> — selector / observer / 操作邊界從窄起（DOM 設計、Reactive 效能的根據）</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> — 值的住址只能一處（CSS 變數、量測一致性的根據）</li>
<li><a href="/blog/report/external-component-collaboration-layers/" data-link-title="跟外部組件合作的層次：離介面越近、合作越穩" data-link-desc="客製外部組件的穩定性與「離組件作者保證的對外介面多遠」成反比。每往內推一層、依賴前提增加、升級風險上升、可逆性下降。本文是 #1 / #5 / #19 / #24 四篇實作的共同抽象。">#45 外部組件合作四層</a> — 離公共介面越近越穩（framework 共處的根據）</li>
<li><a href="/blog/report/compose-feature-at-source-layer/" data-link-title="Feature 操作要跟 Source 同層合成" data-link-desc="Filter / sort / count / transform / search 是 stream 操作、必須跟 stream 的 materialization 同層或更上游合成。在下游做 = 操作 subset 不是 stream。本原則跨前端 UI、後端 API、演算法管線通用、不只是視覺層 vs 資料層。">#64 同層合成</a> — Stream 操作必須跟 materialization 同層（Filter × Source 的本質）</li>
<li><a href="/blog/report/ease-of-writing-vs-intent-alignment/" data-link-title="寫作便利度跟意圖對齊反相關" data-link-desc="寫程式時最容易寫出的版本、通常是離意圖最遠的版本。便利度建立在「現有上下文 / 已 materialize 資料 / 已存在 API」上、而意圖對齊需要找到正確的層、處理上游、跨抽象層 — 兩者方向相反。識別這個反相關 = 識別自己掉進「容易寫的陷阱」。">#67 寫作便利度跟意圖對齊反相關</a> — 容易寫的位置通常是錯位的位置（meta-principle、解釋為什麼層錯位 / 寬 selector / inline style 等便利寫法都會出問題）</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> — Layout test 屬 Ship 前 checkpoint 的具體做法</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> — Playwright 測試的驗證協議：寫完測試 + 第一次跑就 GREEN 是警訊、要先在 buggy code 上看到 RED 才相信測試 catch 到該 catch 的東西</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> — 互動式 UI 的可分享 / 可恢復 / 可導航 state 該寫進 URL（搜尋 / filter / tab / sort / pagination 都該檢視）</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> — DOM 順序預設 = tab 順序、不對齊時優先重排 DOM、tabindex &gt; 0 是反模式</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-原則：寫測試 / refactor / a11y review / Ship 前 case 設計都需要外部觸發（CI / pre-commit / PR template）、不是靠紀律</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> — Search feature 的 capability 維度：prefix vs substring vs fuzzy vs semantic 各自取捨、預設多為 prefix（為 index size）、跟使用者預期不對齊 = silent 失敗</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-框架（呈現 / 策略疊加 / 批次 / 時間 / 選項類型）— 「設計取捨段落」常用的五策略表 + 推薦 + 「先 ship X、Y 下輪」就是這五維度的展現</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> — playwright 測試是字面驗證（input → output 比對）、抓不到「為什麼這個 selector 設計錯」這類行為錯誤、需要 multi-pass review 配合</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.4.0 — 接入 #79 決策對話五維度（對應 #74-#78 系列）；協助前端設計取捨段落的呈現格式對齊 user-facing 決策協議
<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 系列：新增第 7 份 reference <code>data-flow-and-filter-composition</code>（涵蓋 Filter × Source 層錯位 + 五策略 + 跨前端 / 後端 / 演算法 / DB 領域範例）；description 補跨領域 stream 操作觸發詞；SKILL.md 加「相關抽象層原則」段（#42-45 + #64 + #67-68）；強調「不只前端、stream 操作通用」
<strong>Version</strong>: 0.1.0 — 從 <code>content/report/</code> 50+ 篇事後檢討萃取「前端網頁開發 + Playwright 驗證」這條主軸；六份 references 對應「DOM topology / CSS-JS 邊界 / Playwright 三位置 / framework 共處 / Reactive 效能 / A11y」六個情境</p>
]]></content:encoded></item><item><title>Frontend with Playwright — 框架無關的前端開發 + Playwright 驗證</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/</guid><description>&lt;h2 id="這個資料夾是什麼">這個資料夾是什麼&lt;/h2>
&lt;p>&lt;code>frontend-with-playwright&lt;/code> 是一套前端開發協議 skill，原生位置在 &lt;a href="https://github.com/tarrragon/blog/tree/main/.claude/skills/frontend-with-playwright">&lt;code>.claude/skills/frontend-with-playwright/&lt;/code>&lt;/a> 供 Claude runtime 呼叫；這份是&lt;strong>同內容的文章版本&lt;/strong>，讓人類讀者也能直接在 blog 閱讀。&lt;/p>
&lt;p>原則框架無關 — 適用 vanilla HTML/CSS/JS、Vue、React、jQuery — 因為核心是「DOM / CSS / JS 三者的本質行為」加上「Playwright 用 live DOM 量測驗證」、不依賴特定框架的渲染機制。源頭是 &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/frontend-with-playwright/skill/" data-link-title="Frontend with Playwright — SKILL 入口" data-link-desc="框架無關的前端開發 &amp;#43; Playwright 驗證 SKILL 入口：三大支柱、六大原則速查、六份情境 reference 的觸發路由。">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>要寫 CSS 規則、需要先確認 DOM 結構 / selector 該怎麼寫&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/dom-topology-first/" data-link-title="DOM Topology First — 寫 CSS 前先確認 DOM 結構" data-link-desc="frontend-with-playwright reference：寫 CSS 前用 playwright/DevTools 量真實 DOM、selector 三維度設計、起點四選一、idempotency 兩選一。">dom-topology-first&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>不確定 selector 該多寬、命中其他元素&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/dom-topology-first/" data-link-title="DOM Topology First — 寫 CSS 前先確認 DOM 結構" data-link-desc="frontend-with-playwright reference：寫 CSS 前用 playwright/DevTools 量真實 DOM、selector 三維度設計、起點四選一、idempotency 兩選一。">dom-topology-first&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>不確定值該寫進 CSS 還是 JS、CSS layers / variable / class toggle 取捨&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/css-js-boundary/" data-link-title="CSS / JS Boundary — CSS / JS 邊界與 specificity 處理" data-link-desc="frontend-with-playwright reference：CSS-only vs JS-assisted 判準、class toggle 取代 inline style、CSS layers 取代 specificity 戰、variable 單一定義位置、檔案拆分。">css-js-boundary&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>用 &lt;code>!important&lt;/code> / inline style 解 specificity&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/css-js-boundary/" data-link-title="CSS / JS Boundary — CSS / JS 邊界與 specificity 處理" data-link-desc="frontend-with-playwright reference：CSS-only vs JS-assisted 判準、class toggle 取代 inline style、CSS layers 取代 specificity 戰、variable 單一定義位置、檔案拆分。">css-js-boundary&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要用 playwright 驗證 layout / 假設 / 互動&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/playwright-in-loop/" data-link-title="Playwright in the Development Loop — 開發循環的三個位置" data-link-desc="frontend-with-playwright reference：Playwright 三個位置（假設 / 行為 / 互動驗證）的 evaluate 範例、寫成 layout test 的時機與模板、最低門檻 setup。">playwright-in-loop&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Layout bug 第 2 次出現、想寫成測試&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/playwright-in-loop/" data-link-title="Playwright in the Development Loop — 開發循環的三個位置" data-link-desc="frontend-with-playwright reference：Playwright 三個位置（假設 / 行為 / 互動驗證）的 evaluate 範例、寫成 layout test 的時機與模板、最低門檻 setup。">playwright-in-loop&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>客製 UI 被 framework 還原、不知道該注入到哪&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/framework-coexistence/" data-link-title="Framework Coexistence — 跟 framework-managed DOM 共處" data-link-desc="frontend-with-playwright reference：framework 邊界辨識、JS 操作四級安全度、客製 UI 注入到邊界外、外部組件四層合作（公共介面 → 邊界 → 邊界 DOM → 內部結構）。">framework-coexistence&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要客製外部組件（pagefind / vendor library）&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/framework-coexistence/" data-link-title="Framework Coexistence — 跟 framework-managed DOM 共處" data-link-desc="frontend-with-playwright reference：framework 邊界辨識、JS 操作四級安全度、客製 UI 注入到邊界外、外部組件四層合作（公共介面 → 邊界 → 邊界 DOM → 內部結構）。">framework-coexistence&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>使用者反映卡頓、CPU 100%、scroll lag、resize jank&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/reactive-performance/" data-link-title="Reactive Performance — Reactive 效能盤點與優化" data-link-desc="frontend-with-playwright reference：MutationObserver 三維度、polling → observer、iteration / regex 成本、layout reflow、resource 載入時序、reactive listener 盤點協議。">reactive-performance&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要設計 MutationObserver / event listener 範圍&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/reactive-performance/" data-link-title="Reactive Performance — Reactive 效能盤點與優化" data-link-desc="frontend-with-playwright reference：MutationObserver 三維度、polling → observer、iteration / regex 成本、layout reflow、resource 載入時序、reactive listener 盤點協議。">reactive-performance&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要驗收鍵盤 / screen reader / motor / 視覺 a11y&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/accessibility-and-focus/" data-link-title="Accessibility and Focus — A11y 三道防線" data-link-desc="frontend-with-playwright reference：鍵盤可達性三要素、focus management on DOM move、aria-live 動態廣播、Native HTML &amp;gt; ARIA、視覺 / motor a11y。">accessibility-and-focus&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>JS reparent 後 focus 跑掉、aria-live 沒朗讀&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/accessibility-and-focus/" data-link-title="Accessibility and Focus — A11y 三道防線" data-link-desc="frontend-with-playwright reference：鍵盤可達性三要素、focus management on DOM move、aria-live 動態廣播、Native HTML &amp;gt; ARIA、視覺 / motor a11y。">accessibility-and-focus&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>設計 filter / sort / count 操作、source 是分批 / streaming&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/" data-link-title="Data Flow and Filter Composition — Filter × Source 層錯位與五策略" data-link-desc="frontend-with-playwright reference：Filter / sort / count / transform stream 操作的層錯位識別 &amp;#43; 五策略合成。原則跨前端 / 後端 / 演算法 / DB 通用、playwright 驗證模板。">data-flow-and-filter-composition&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>「Load more 後畫面閃但內容沒變」的 silent 缺口&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/" data-link-title="Data Flow and Filter Composition — Filter × Source 層錯位與五策略" data-link-desc="frontend-with-playwright reference：Filter / sort / count / transform stream 操作的層錯位識別 &amp;#43; 五策略合成。原則跨前端 / 後端 / 演算法 / DB 通用、playwright 驗證模板。">data-flow-and-filter-composition&lt;/a>（層錯位）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Backend / 演算法 / map-reduce 的 post-filter 漏項&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/" data-link-title="Data Flow and Filter Composition — Filter × Source 層錯位與五策略" data-link-desc="frontend-with-playwright reference：Filter / sort / count / transform stream 操作的層錯位識別 &amp;#43; 五策略合成。原則跨前端 / 後端 / 演算法 / DB 通用、playwright 驗證模板。">data-flow-and-filter-composition&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>frontend-with-playwright</code> 是一套前端開發協議 skill，原生位置在 <a href="https://github.com/tarrragon/blog/tree/main/.claude/skills/frontend-with-playwright"><code>.claude/skills/frontend-with-playwright/</code></a> 供 Claude runtime 呼叫；這份是<strong>同內容的文章版本</strong>，讓人類讀者也能直接在 blog 閱讀。</p>
<p>原則框架無關 — 適用 vanilla HTML/CSS/JS、Vue、React、jQuery — 因為核心是「DOM / CSS / JS 三者的本質行為」加上「Playwright 用 live DOM 量測驗證」、不依賴特定框架的渲染機制。源頭是 <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/frontend-with-playwright/skill/" data-link-title="Frontend with Playwright — SKILL 入口" data-link-desc="框架無關的前端開發 &#43; Playwright 驗證 SKILL 入口：三大支柱、六大原則速查、六份情境 reference 的觸發路由。">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>要寫 CSS 規則、需要先確認 DOM 結構 / selector 該怎麼寫</td>
          <td><a href="/blog/skills/frontend-with-playwright/dom-topology-first/" data-link-title="DOM Topology First — 寫 CSS 前先確認 DOM 結構" data-link-desc="frontend-with-playwright reference：寫 CSS 前用 playwright/DevTools 量真實 DOM、selector 三維度設計、起點四選一、idempotency 兩選一。">dom-topology-first</a></td>
      </tr>
      <tr>
          <td>不確定 selector 該多寬、命中其他元素</td>
          <td><a href="/blog/skills/frontend-with-playwright/dom-topology-first/" data-link-title="DOM Topology First — 寫 CSS 前先確認 DOM 結構" data-link-desc="frontend-with-playwright reference：寫 CSS 前用 playwright/DevTools 量真實 DOM、selector 三維度設計、起點四選一、idempotency 兩選一。">dom-topology-first</a></td>
      </tr>
      <tr>
          <td>不確定值該寫進 CSS 還是 JS、CSS layers / variable / class toggle 取捨</td>
          <td><a href="/blog/skills/frontend-with-playwright/css-js-boundary/" data-link-title="CSS / JS Boundary — CSS / JS 邊界與 specificity 處理" data-link-desc="frontend-with-playwright reference：CSS-only vs JS-assisted 判準、class toggle 取代 inline style、CSS layers 取代 specificity 戰、variable 單一定義位置、檔案拆分。">css-js-boundary</a></td>
      </tr>
      <tr>
          <td>用 <code>!important</code> / inline style 解 specificity</td>
          <td><a href="/blog/skills/frontend-with-playwright/css-js-boundary/" data-link-title="CSS / JS Boundary — CSS / JS 邊界與 specificity 處理" data-link-desc="frontend-with-playwright reference：CSS-only vs JS-assisted 判準、class toggle 取代 inline style、CSS layers 取代 specificity 戰、variable 單一定義位置、檔案拆分。">css-js-boundary</a></td>
      </tr>
      <tr>
          <td>要用 playwright 驗證 layout / 假設 / 互動</td>
          <td><a href="/blog/skills/frontend-with-playwright/playwright-in-loop/" data-link-title="Playwright in the Development Loop — 開發循環的三個位置" data-link-desc="frontend-with-playwright reference：Playwright 三個位置（假設 / 行為 / 互動驗證）的 evaluate 範例、寫成 layout test 的時機與模板、最低門檻 setup。">playwright-in-loop</a></td>
      </tr>
      <tr>
          <td>Layout bug 第 2 次出現、想寫成測試</td>
          <td><a href="/blog/skills/frontend-with-playwright/playwright-in-loop/" data-link-title="Playwright in the Development Loop — 開發循環的三個位置" data-link-desc="frontend-with-playwright reference：Playwright 三個位置（假設 / 行為 / 互動驗證）的 evaluate 範例、寫成 layout test 的時機與模板、最低門檻 setup。">playwright-in-loop</a></td>
      </tr>
      <tr>
          <td>客製 UI 被 framework 還原、不知道該注入到哪</td>
          <td><a href="/blog/skills/frontend-with-playwright/framework-coexistence/" data-link-title="Framework Coexistence — 跟 framework-managed DOM 共處" data-link-desc="frontend-with-playwright reference：framework 邊界辨識、JS 操作四級安全度、客製 UI 注入到邊界外、外部組件四層合作（公共介面 → 邊界 → 邊界 DOM → 內部結構）。">framework-coexistence</a></td>
      </tr>
      <tr>
          <td>要客製外部組件（pagefind / vendor library）</td>
          <td><a href="/blog/skills/frontend-with-playwright/framework-coexistence/" data-link-title="Framework Coexistence — 跟 framework-managed DOM 共處" data-link-desc="frontend-with-playwright reference：framework 邊界辨識、JS 操作四級安全度、客製 UI 注入到邊界外、外部組件四層合作（公共介面 → 邊界 → 邊界 DOM → 內部結構）。">framework-coexistence</a></td>
      </tr>
      <tr>
          <td>使用者反映卡頓、CPU 100%、scroll lag、resize jank</td>
          <td><a href="/blog/skills/frontend-with-playwright/reactive-performance/" data-link-title="Reactive Performance — Reactive 效能盤點與優化" data-link-desc="frontend-with-playwright reference：MutationObserver 三維度、polling → observer、iteration / regex 成本、layout reflow、resource 載入時序、reactive listener 盤點協議。">reactive-performance</a></td>
      </tr>
      <tr>
          <td>要設計 MutationObserver / event listener 範圍</td>
          <td><a href="/blog/skills/frontend-with-playwright/reactive-performance/" data-link-title="Reactive Performance — Reactive 效能盤點與優化" data-link-desc="frontend-with-playwright reference：MutationObserver 三維度、polling → observer、iteration / regex 成本、layout reflow、resource 載入時序、reactive listener 盤點協議。">reactive-performance</a></td>
      </tr>
      <tr>
          <td>要驗收鍵盤 / screen reader / motor / 視覺 a11y</td>
          <td><a href="/blog/skills/frontend-with-playwright/accessibility-and-focus/" data-link-title="Accessibility and Focus — A11y 三道防線" data-link-desc="frontend-with-playwright reference：鍵盤可達性三要素、focus management on DOM move、aria-live 動態廣播、Native HTML &gt; ARIA、視覺 / motor a11y。">accessibility-and-focus</a></td>
      </tr>
      <tr>
          <td>JS reparent 後 focus 跑掉、aria-live 沒朗讀</td>
          <td><a href="/blog/skills/frontend-with-playwright/accessibility-and-focus/" data-link-title="Accessibility and Focus — A11y 三道防線" data-link-desc="frontend-with-playwright reference：鍵盤可達性三要素、focus management on DOM move、aria-live 動態廣播、Native HTML &gt; ARIA、視覺 / motor a11y。">accessibility-and-focus</a></td>
      </tr>
      <tr>
          <td>設計 filter / sort / count 操作、source 是分批 / streaming</td>
          <td><a href="/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/" data-link-title="Data Flow and Filter Composition — Filter × Source 層錯位與五策略" data-link-desc="frontend-with-playwright reference：Filter / sort / count / transform stream 操作的層錯位識別 &#43; 五策略合成。原則跨前端 / 後端 / 演算法 / DB 通用、playwright 驗證模板。">data-flow-and-filter-composition</a></td>
      </tr>
      <tr>
          <td>「Load more 後畫面閃但內容沒變」的 silent 缺口</td>
          <td><a href="/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/" data-link-title="Data Flow and Filter Composition — Filter × Source 層錯位與五策略" data-link-desc="frontend-with-playwright reference：Filter / sort / count / transform stream 操作的層錯位識別 &#43; 五策略合成。原則跨前端 / 後端 / 演算法 / DB 通用、playwright 驗證模板。">data-flow-and-filter-composition</a>（層錯位）</td>
      </tr>
      <tr>
          <td>Backend / 演算法 / map-reduce 的 post-filter 漏項</td>
          <td><a href="/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/" data-link-title="Data Flow and Filter Composition — Filter × Source 層錯位與五策略" data-link-desc="frontend-with-playwright reference：Filter / sort / count / transform stream 操作的層錯位識別 &#43; 五策略合成。原則跨前端 / 後端 / 演算法 / DB 通用、playwright 驗證模板。">data-flow-and-filter-composition</a>（跨領域同結構）</td>
      </tr>
  </tbody>
</table>
<p>每份 reference 自包含：讀任一份不需要回頭讀其他 reference。</p>
<h2 id="跟-requirement-protocol-的關係">跟 requirement-protocol 的關係</h2>
<p><a href="/blog/skills/requirement-protocol/" data-link-title="Requirement Protocol — 需求確認到實作的對話協議" data-link-desc="從需求確認到實作的對話協議：模糊指令澄清、可決定 vs 該確認、失敗 2 次轉折、覆寫成本告知、revert checkpoint、漸進驗證、工具切換時機。六大原則 &#43; 五份情境 reference。">requirement-protocol</a> 是上層的「對話協議」（澄清需求、失敗轉折、覆寫成本、工具切換時機）；本 skill 是下層的「前端執行協議」（DOM / CSS / JS / Playwright 的具體做法）。</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>該讀哪個 skill</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>不確定該怎麼跟使用者溝通、需求模糊、失敗該怎麼轉折</td>
          <td>requirement-protocol</td>
      </tr>
      <tr>
          <td>知道要做什麼、不確定前端該怎麼實作驗證</td>
          <td>frontend-with-playwright（本 skill）</td>
      </tr>
  </tbody>
</table>
<p>兩個 skill 的 <code>playwright</code> 段落互補：requirement-protocol 講「何時切」、本 skill 講「切了之後具體寫什麼 query」。</p>
<h2 id="與-blog-專案其他資料的關係">與 blog 專案其他資料的關係</h2>
<table>
  <thead>
      <tr>
          <th>位置</th>
          <th>角色</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>.claude/skills/frontend-with-playwright/</code></td>
          <td>實際 skill — Claude runtime 呼叫的檔案來源</td>
      </tr>
      <tr>
          <td><code>content/skills/frontend-with-playwright/</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><a href="/blog/skills/requirement-protocol/" data-link-title="Requirement Protocol — 需求確認到實作的對話協議" data-link-desc="從需求確認到實作的對話協議：模糊指令澄清、可決定 vs 該確認、失敗 2 次轉折、覆寫成本告知、revert checkpoint、漸進驗證、工具切換時機。六大原則 &#43; 五份情境 reference。"><code>content/skills/requirement-protocol/</code></a></td>
          <td>上層對話協議 skill</td>
      </tr>
  </tbody>
</table>
<h2 id="last-updated">Last Updated</h2>
<p>2026-04-26 — v0.2.0 接入 #55-#68 系列：新增第 7 份 reference <code>data-flow-and-filter-composition</code>（Filter × Source 層錯位 + 五策略 + 跨前端 / 後端 / 演算法 / DB 範例）；強調原則跨領域通用、不只前端。</p>
<p>歷史版本：</p>
<ul>
<li>2026-04-26 — v0.1.0 初版：六份 references 對應「DOM topology / CSS-JS 邊界 / Playwright 三位置 / framework 共處 / Reactive 效能 / A11y」六個情境</li>
</ul>
]]></content:encoded></item><item><title>Playwright in the Development Loop — 開發循環的三個位置</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/playwright-in-loop/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/playwright-in-loop/</guid><description>&lt;p>Playwright 在前端開發循環的三個位置：假設驗證（寫 CSS 前）、行為驗證（規則寫完後）、互動驗證（dispatch event 後）。第 2 次同個版型 bug 出現 → 寫成測試固化。&lt;/p>
&lt;p>適用：CSS / DOM debug、layout 驗收、互動行為驗證、寫 layout regression test。
不適用：純 unit test（function input/output、無 DOM）— 那用 Vitest / Jest 即可。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋三個位置的具體 query 範例、layout test 模板、最低門檻 setup。&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>即將寫 CSS 規則、想先確認 DOM 結構&lt;/td>
 &lt;td>位置 1：假設驗證 — 量 ancestor chain&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>規則寫完、想確認實際 layout 對&lt;/td>
 &lt;td>位置 2：行為驗證 — 量 bounding rect&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>想驗證使用者互動後的狀態（filter / search / click）&lt;/td>
 &lt;td>位置 3：互動驗證 — dispatch event&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>同個 layout bug 第 2 次出現&lt;/td>
 &lt;td>寫 layout test、CI 防回歸&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>不確定 server 怎麼起 / 怎麼接 playwright&lt;/td>
 &lt;td>看下方「最低門檻 setup」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼-playwright-是前端開發的核心驗證工具">為什麼 playwright 是前端開發的核心驗證工具&lt;/h2>
&lt;p>CSS / DOM 的真實狀態 = 規則 + DOM tree + 樣式繼承 + 框架渲染的合成結果。靜態推理只能基於假設、視覺截圖只能傳達結果不傳達原因。&lt;/p>
&lt;p>Playwright &lt;code>browser_evaluate&lt;/code> 直接執行 JS 在 live page、返回 DOM tree / computed style / bounding rect — &lt;strong>把假設變成量測值&lt;/strong>。寫一個 evaluate fn ≈ 30 行 JS，比反覆推理快得多。&lt;/p>
&lt;hr>
&lt;h2 id="位置-1假設驗證寫-css-規則前">位置 1：假設驗證（寫 CSS 規則前）&lt;/h2>
&lt;h3 id="量-ancestor-chain">量 ancestor chain&lt;/h3>





&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">el&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;.target&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">el&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;h3 id="量子節點與-sibling">量子節點與 sibling&lt;/h3>





&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">parent&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&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="k">return&lt;/span> &lt;span class="nb">Array&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">parent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">children&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">c&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">c&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">4&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="量元素是否存在--數量">量元素是否存在 / 數量&lt;/h3>





&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="nx">count&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">querySelectorAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;.result&amp;#39;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">length&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="nx">first&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;.result&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">outerHTML&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">200&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="p">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>寫 CSS 規則前 30 秒能省掉後續 30 分鐘推理。&lt;/p>
&lt;hr>
&lt;h2 id="位置-2行為驗證規則寫完後">位置 2：行為驗證（規則寫完後）&lt;/h2>
&lt;h3 id="量-bounding-rect">量 bounding rect&lt;/h3>





&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="nx">form&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__form&amp;#39;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">getBoundingClientRect&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="nx">scope&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;.scope&amp;#39;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">getBoundingClientRect&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="nx">results&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;.results&amp;#39;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">getBoundingClientRect&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="p">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>返回 &lt;code>{x, y, width, height, top, right, bottom, left}&lt;/code> 的純物件、能直接 assert 順序與位置。&lt;/p>
&lt;h3 id="量-computed-style">量 computed style&lt;/h3>





&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">el&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;.target&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="kr">const&lt;/span> &lt;span class="nx">cs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">getComputedStyle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">el&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">return&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">display&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">cs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">display&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">position&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">cs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">position&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="nx">gridRow&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">cs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">gridRow&lt;/span>&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="nx">color&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">cs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">color&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="nx">zIndex&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">cs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">zIndex&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="量實際贏的-css-rule">量「實際贏的 CSS rule」&lt;/h3>





&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">el&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;.target&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="c1">// CSSOM 沒提供標準 getMatchedCSSRules；用 computed style 加 inspect
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nx">getComputedStyle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">el&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">cssText&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 全部 computed properties
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>或在 DevTools Computed panel 看 — 但 playwright 能寫成測試重跑。&lt;/p></description><content:encoded><![CDATA[<p>Playwright 在前端開發循環的三個位置：假設驗證（寫 CSS 前）、行為驗證（規則寫完後）、互動驗證（dispatch event 後）。第 2 次同個版型 bug 出現 → 寫成測試固化。</p>
<p>適用：CSS / DOM debug、layout 驗收、互動行為驗證、寫 layout regression test。
不適用：純 unit test（function input/output、無 DOM）— 那用 Vitest / Jest 即可。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋三個位置的具體 query 範例、layout test 模板、最低門檻 setup。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>即將寫 CSS 規則、想先確認 DOM 結構</td>
          <td>位置 1：假設驗證 — 量 ancestor chain</td>
      </tr>
      <tr>
          <td>規則寫完、想確認實際 layout 對</td>
          <td>位置 2：行為驗證 — 量 bounding rect</td>
      </tr>
      <tr>
          <td>想驗證使用者互動後的狀態（filter / search / click）</td>
          <td>位置 3：互動驗證 — dispatch event</td>
      </tr>
      <tr>
          <td>同個 layout bug 第 2 次出現</td>
          <td>寫 layout test、CI 防回歸</td>
      </tr>
      <tr>
          <td>不確定 server 怎麼起 / 怎麼接 playwright</td>
          <td>看下方「最低門檻 setup」</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼-playwright-是前端開發的核心驗證工具">為什麼 playwright 是前端開發的核心驗證工具</h2>
<p>CSS / DOM 的真實狀態 = 規則 + DOM tree + 樣式繼承 + 框架渲染的合成結果。靜態推理只能基於假設、視覺截圖只能傳達結果不傳達原因。</p>
<p>Playwright <code>browser_evaluate</code> 直接執行 JS 在 live page、返回 DOM tree / computed style / bounding rect — <strong>把假設變成量測值</strong>。寫一個 evaluate fn ≈ 30 行 JS，比反覆推理快得多。</p>
<hr>
<h2 id="位置-1假設驗證寫-css-規則前">位置 1：假設驗證（寫 CSS 規則前）</h2>
<h3 id="量-ancestor-chain">量 ancestor chain</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">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="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">el</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><h3 id="量子節點與-sibling">量子節點與 sibling</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">parent</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&#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">parent</span><span class="p">.</span><span class="nx">children</span><span class="p">).</span><span class="nx">map</span><span class="p">(</span><span class="nx">c</span> <span class="p">=&gt;</span> <span class="sb">`</span><span class="si">${</span><span class="nx">c</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">c</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">4</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><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">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">count</span><span class="o">:</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 class="nx">length</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">first</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;.result&#39;</span><span class="p">)</span><span class="o">?</span><span class="p">.</span><span class="nx">outerHTML</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">200</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><p>寫 CSS 規則前 30 秒能省掉後續 30 分鐘推理。</p>
<hr>
<h2 id="位置-2行為驗證規則寫完後">位置 2：行為驗證（規則寫完後）</h2>
<h3 id="量-bounding-rect">量 bounding rect</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="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;.pagefind-ui__form&#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">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">4</span><span class="cl">  <span class="nx">results</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;.results&#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="p">})</span></span></span></code></pre></div><p>返回 <code>{x, y, width, height, top, right, bottom, left}</code> 的純物件、能直接 assert 順序與位置。</p>
<h3 id="量-computed-style">量 computed style</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">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="kr">const</span> <span class="nx">cs</span> <span class="o">=</span> <span class="nx">getComputedStyle</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nx">display</span><span class="o">:</span> <span class="nx">cs</span><span class="p">.</span><span class="nx">display</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nx">position</span><span class="o">:</span> <span class="nx">cs</span><span class="p">.</span><span class="nx">position</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nx">gridRow</span><span class="o">:</span> <span class="nx">cs</span><span class="p">.</span><span class="nx">gridRow</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nx">color</span><span class="o">:</span> <span class="nx">cs</span><span class="p">.</span><span class="nx">color</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nx">zIndex</span><span class="o">:</span> <span class="nx">cs</span><span class="p">.</span><span class="nx">zIndex</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="量實際贏的-css-rule">量「實際贏的 CSS rule」</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">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="c1">// CSSOM 沒提供標準 getMatchedCSSRules；用 computed style 加 inspect
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="k">return</span> <span class="nx">getComputedStyle</span><span class="p">(</span><span class="nx">el</span><span class="p">).</span><span class="nx">cssText</span><span class="p">;</span>  <span class="c1">// 全部 computed properties
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>或在 DevTools Computed panel 看 — 但 playwright 能寫成測試重跑。</p>
<hr>
<h2 id="位置-3互動驗證dispatch-event-後讀-state">位置 3：互動驗證（dispatch event 後讀 state）</h2>
<h3 id="模擬-input">模擬 input</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 class="c1">// 等 debounce / async render
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span>  <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><h3 id="模擬-click">模擬 click</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="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.scope-toggle button[data-scope=&#34;title&#34;]&#39;</span><span class="p">).</span><span class="nx">click</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">3</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">500</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nx">activeScope</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-toggle [aria-pressed=&#34;true&#34;]&#39;</span><span class="p">)</span><span class="o">?</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">scope</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nx">visibleResults</span><span class="o">:</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">&#39;.result:not([hidden])&#39;</span><span class="p">).</span><span class="nx">length</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><h3 id="模擬-viewport-resize透過-playwright-api不在-evaluate-內">模擬 viewport resize（透過 playwright API、不在 evaluate 內）</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">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">2</span><span class="cl"><span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">evaluate</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">layout</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;.layout&#39;</span><span class="p">).</span><span class="nx">getBoundingClientRect</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">sidebarVisible</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;.sidebar&#39;</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">5</span><span class="cl"><span class="p">}));</span></span></span></code></pre></div><hr>
<h2 id="第-2-次同個-bug--寫成-layout-測試固化">第 2 次同個 bug → 寫成 layout 測試固化</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="kr">import</span> <span class="p">{</span> <span class="nx">test</span><span class="p">,</span> <span class="nx">expect</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;@playwright/test&#39;</span><span class="p">;</span>
</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 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"> 4</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"> 5</span><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForSelector</span><span class="p">(</span><span class="s1">&#39;.result&#39;</span><span class="p">);</span>
</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">  <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"> 8</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"> 9</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">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</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">12</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">13</span><span class="cl"><span class="p">});</span>
</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 class="nx">test</span><span class="p">(</span><span class="s1">&#39;sidebar visible at 1400px+&#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">16</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">1400</span><span class="p">,</span> <span class="nx">height</span><span class="o">:</span> <span class="mi">800</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">17</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">18</span><span class="cl">  <span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">&#39;.sidebar&#39;</span><span class="p">)).</span><span class="nx">toBeVisible</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="nx">test</span><span class="p">(</span><span class="s1">&#39;sidebar hidden at &lt; 1400px&#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">22</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">1399</span><span class="p">,</span> <span class="nx">height</span><span class="o">:</span> <span class="mi">800</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">23</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">24</span><span class="cl">  <span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">&#39;.sidebar&#39;</span><span class="p">)).</span><span class="nx">toBeHidden</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>未來 layout 改動觸發 regression、CI 立刻發現。</p>
<hr>
<h2 id="寫-layout-test-的優先順序">寫 layout test 的優先順序</h2>
<p>不要每個 layout 都寫測試 — 寫測試的 ROI 條件：</p>
<table>
  <thead>
      <tr>
          <th>條件</th>
          <th>該寫測試嗎</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Bug 第 1 次出現</td>
          <td>否（修了就好）</td>
      </tr>
      <tr>
          <td>Bug 第 2 次出現</td>
          <td><strong>是</strong>（防回歸）</td>
      </tr>
      <tr>
          <td>Layout 跟 viewport 強相關（breakpoint）</td>
          <td>是（容易壞）</td>
      </tr>
      <tr>
          <td>Layout 跟 framework 重渲染相關</td>
          <td>是（升級時需要驗證）</td>
      </tr>
      <tr>
          <td>純視覺風格（顏色 / 字型）</td>
          <td>否（用視覺 review 即可）</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="最低門檻-setup">最低門檻 setup</h2>
<h3 id="server">Server</h3>





<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">hugo server                                       <span class="c1"># Hugo</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">python3 -m http.server <span class="m">8000</span> --directory public    <span class="c1"># 純靜態</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">npm run dev                                        <span class="c1"># framework dev server</span></span></span></code></pre></div><h3 id="playwright-mcp給-claude-用">Playwright MCP（給 Claude 用）</h3>
<p>Claude 透過 MCP 提供的 tool：</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>
<li><code>browser_click(selector)</code> / <code>browser_type(selector, text)</code> — 互動</li>
</ul>
<h3 id="playwright-測試給-ci-用">Playwright 測試（給 CI 用）</h3>





<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">npm i -D @playwright/test
</span></span><span class="line"><span class="ln">2</span><span class="cl">npx playwright install
</span></span><span class="line"><span class="ln">3</span><span class="cl">npx playwright test</span></span></code></pre></div><p><code>playwright.config.ts</code> 設 baseURL 指向 <code>http://localhost:1313</code>（Hugo 預設）或自訂 port。</p>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1css-不生效">範例 1：CSS 不生效</h3>
<p><strong>錯</strong>：靜態推理 + 截圖溝通 4 次失敗。</p>
<p><strong>對</strong>：第 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="c1">// 1. 確認 ancestor chain
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><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"> 3</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"> 4</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">el</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="k">while</span> <span class="p">(</span><span class="nx">n</span><span class="p">)</span> <span class="p">{</span> <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 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 class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</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"> 7</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">// → 看到目標元素是 form 的 child、不是 .pagefind-ui 的直接 child
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// 2. 確認 computed style 誰贏
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><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">12</span><span class="cl"><span class="c1">// → &#34;rgb(0,0,255)&#34; — vendor 的藍色贏了
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1">// 3. 換方向：用 @layer 把 vendor 包起來
</span></span></span></code></pre></div><h3 id="範例-2layout-第-2-次出現一樣的-bug">範例 2：Layout 第 2 次出現一樣的 bug</h3>
<p><strong>錯</strong>：手動在不同 viewport 下視覺驗證、commit、過幾週又壞、又手動驗證。</p>
<p><strong>對</strong>：第 2 次出現後寫成測試：</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;layout golden path: form → scope → 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="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">width</span> <span class="k">of</span> <span class="p">[</span><span class="mi">375</span><span class="p">,</span> <span class="mi">768</span><span class="p">,</span> <span class="mi">1024</span><span class="p">,</span> <span class="mi">1400</span><span class="p">,</span> <span class="mi">1920</span><span class="p">])</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</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="p">,</span> <span class="nx">height</span><span class="o">:</span> <span class="mi">800</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">4</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">5</span><span class="cl">    <span class="kr">const</span> <span class="nx">form</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">6</span><span class="cl">    <span class="kr">const</span> <span class="nx">scope</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">7</span><span class="cl">    <span class="nx">expect</span><span class="p">(</span><span class="nx">scope</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> <span class="sb">`at width=</span><span class="si">${</span><span class="nx">width</span><span class="si">}</span><span class="sb">`</span><span class="p">).</span><span class="nx">toBeGreaterThanOrEqual</span><span class="p">(</span><span class="nx">form</span><span class="p">.</span><span class="nx">y</span> <span class="o">+</span> <span class="nx">form</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><span class="line"><span class="ln">9</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>未來改 CSS、CI 直接告訴你哪個 viewport 壞了。</p>
<hr>
<h2 id="red-green-順序先看到-red-才相信-green">RED-GREEN 順序：先看到 RED 才相信 GREEN</h2>
<p>寫完 playwright test 後、必須先在「buggy code」跑出 RED 才能相信「fixed code」的 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>。</p>
<p>修 bug 的順序：</p>
<ol>
<li><strong>先寫測試 + 跑 → RED</strong>（在 buggy code 上 fail、證明測試會 catch + bug 真的存在）</li>
<li><strong>修 code</strong></li>
<li><strong>跑測試 → GREEN</strong>（證明修對了 + 測試會抓回歸）</li>
</ol>
<p>跳過 step 1 的 retrospective 補救（修完才補測試）：</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"># Stash 修復、checkout 修前 commit</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">git stash <span class="o">&amp;&amp;</span> git checkout &lt;pre-fix-commit&gt;
</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"># Cherry-pick 測試 commit、build、跑</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">git cherry-pick &lt;test-commit&gt;
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">make site <span class="o">&amp;&amp;</span> npm <span class="nb">test</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 預期：RED</span>
</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 class="c1"># 切回修後版本</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">git checkout main <span class="o">&amp;&amp;</span> git stash pop
</span></span><span class="line"><span class="ln">11</span><span class="cl">npm <span class="nb">test</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 預期：GREEN</span></span></span></code></pre></div><p>兩個訊號都看到 + 順序對、測試才被驗證。</p>
<hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>debug / 驗證 layout 時：</p>
<ul>
<li><input disabled="" type="checkbox"> 寫 CSS 規則前、有沒有用 playwright 量過 ancestor chain？</li>
<li><input disabled="" type="checkbox"> 規則寫完後、有沒有用 playwright 量過 bounding rect / computed style 確認？</li>
<li><input disabled="" type="checkbox"> 互動行為（filter / click）有沒有用 playwright 模擬 + 量化驗證？</li>
<li><input disabled="" type="checkbox"> 同個 layout bug 第 2 次出現時、有沒有寫成測試？</li>
<li><input disabled="" type="checkbox"> 推理失敗 ≥ 2 次時、有沒有主動切換到 playwright（不等到第 5 次）？</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/layout-tests-with-playwright/" data-link-title="用前端測試把排版問題自動化" data-link-desc="排版問題傳統靠人眼檢查、容易遺漏邊界 case。當一個版型被 debug 兩次以上、就值得寫成 playwright 測試把規範固定下來。本文展開測試替代手動檢查的時機。">layout-tests-with-playwright</a> — 用前端測試把排版問題自動化</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>Reactive Performance — Reactive 效能盤點與優化</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/reactive-performance/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/reactive-performance/</guid><description>&lt;p>前端 reactive 效能的盤點與優化：MutationObserver 三維度（root / options / debounce）、polling → observer、iteration / regex / reflow / lazy load 四個成本面。&lt;/p>
&lt;p>適用：使用者反映卡頓、CPU 100%、scroll lag、resize jank、首次互動延遲。
不適用：純後端效能、純伺服器渲染（SSR 的成本另一套）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋四個效能風險面向、observer 設計準則、量測方法。&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>量 input listener / observer 觸發頻率&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Scroll 時掉幀&lt;/td>
 &lt;td>量 scroll listener 觸發頻率 + reflow 成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Resize 視窗時 layout 跳動&lt;/td>
 &lt;td>量 ResizeObserver 觸發 + 重新計算成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CPU 100%、即使頁面靜止&lt;/td>
 &lt;td>找 setInterval / setTimeout polling、換 observer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>結果規模大（&amp;gt; 500 筆）時慢&lt;/td>
 &lt;td>量 iteration cost、看是否每筆都跑 regex&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>首次互動延遲（搜尋頁 200ms+ 才能輸入）&lt;/td>
 &lt;td>量 critical path、看 lazy chunk 是否要 preload&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將寫 &lt;code>observer.observe(document.body, { subtree: true })&lt;/code>&lt;/td>
 &lt;td>停 — 範圍過寬、補上限制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼-reactive-效能要主動盤點">為什麼 reactive 效能要主動盤點&lt;/h2>
&lt;p>Reactive 系統的成本不是線性 — 一個觸發頻率失控的 listener 會放大整個系統的負擔：&lt;/p>
&lt;ul>
&lt;li>一個 observer 觸發 → callback 執行 → DOM 變動 → 再觸發 observer → 無限迴圈&lt;/li>
&lt;li>一個 input listener 沒 debounce → 每個鍵盤事件跑一次重 query → CPU 飆高&lt;/li>
&lt;li>一個 setInterval polling 50ms → 永遠不停、即使頁面背景&lt;/li>
&lt;/ul>
&lt;p>主動盤點 = 寫之前先估觸發頻率、寫之後用 &lt;code>console.count&lt;/code> 驗證。事後 debug 比事前設計貴 10 倍。&lt;/p>
&lt;hr>
&lt;h2 id="風險面向-1listener-觸發頻率">風險面向 1：Listener 觸發頻率&lt;/h2>
&lt;h3 id="mutationobserver-三維度">MutationObserver 三維度&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>Root&lt;/td>
 &lt;td>最窄（具體 element）&lt;/td>
 &lt;td>&lt;code>document.body&lt;/code> / &lt;code>document.documentElement&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Options&lt;/td>
 &lt;td>&lt;code>{ childList: true }&lt;/code>&lt;/td>
 &lt;td>&lt;code>{ subtree: true, attributes: true, characterData: true }&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Debounce&lt;/td>
 &lt;td>0ms 或微 microtask&lt;/td>
 &lt;td>沒寫 debounce、callback 執行 &amp;gt; 5ms&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="過寬範例">過寬範例&lt;/h3>





&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="c1">// 監聽整個 page 任何變動
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">MutationObserver&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">cb&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">observe&lt;/span>&lt;span class="p">(&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">3&lt;/span>&lt;span class="cl"> &lt;span class="nx">childList&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&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="nx">subtree&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&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">attributes&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&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">characterData&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&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="c1">// 一次 react state 變動 → 100+ 個 callback
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="對例">對例&lt;/h3>





&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">const&lt;/span> &lt;span class="nx">root&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__results-area&amp;#39;&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="kd">let&lt;/span> &lt;span class="nx">timer&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="k">new&lt;/span> &lt;span class="nx">MutationObserver&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">4&lt;/span>&lt;span class="cl"> &lt;span class="nx">clearTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">timer&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">timer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">callback&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">100&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// debounce 100ms
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}).&lt;/span>&lt;span class="nx">observe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">root&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">childList&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&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="c1">// 只監聽 results 直接子節點變動、debounce 100ms
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="量觸發頻率">量觸發頻率&lt;/h3>





&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="kd">let&lt;/span> &lt;span class="nx">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&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="k">new&lt;/span> &lt;span class="nx">MutationObserver&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">3&lt;/span>&lt;span class="cl"> &lt;span class="nx">count&lt;/span>&lt;span class="o">++&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="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;mutation&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">count&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="p">}).&lt;/span>&lt;span class="nx">observe&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>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1">// 預期：使用者打字 1 秒、觸發 10 次以下
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="c1">// 觀察：100+ 次 → 範圍過寬、加 debounce 或縮 root
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>或用 &lt;code>console.count('decorate')&lt;/code> 計數、看每秒觸發幾次。&lt;/p></description><content:encoded><![CDATA[<p>前端 reactive 效能的盤點與優化：MutationObserver 三維度（root / options / debounce）、polling → observer、iteration / regex / reflow / lazy load 四個成本面。</p>
<p>適用：使用者反映卡頓、CPU 100%、scroll lag、resize jank、首次互動延遲。
不適用：純後端效能、純伺服器渲染（SSR 的成本另一套）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋四個效能風險面向、observer 設計準則、量測方法。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>使用者打字時搜尋頁卡頓</td>
          <td>量 input listener / observer 觸發頻率</td>
      </tr>
      <tr>
          <td>Scroll 時掉幀</td>
          <td>量 scroll listener 觸發頻率 + reflow 成本</td>
      </tr>
      <tr>
          <td>Resize 視窗時 layout 跳動</td>
          <td>量 ResizeObserver 觸發 + 重新計算成本</td>
      </tr>
      <tr>
          <td>CPU 100%、即使頁面靜止</td>
          <td>找 setInterval / setTimeout polling、換 observer</td>
      </tr>
      <tr>
          <td>結果規模大（&gt; 500 筆）時慢</td>
          <td>量 iteration cost、看是否每筆都跑 regex</td>
      </tr>
      <tr>
          <td>首次互動延遲（搜尋頁 200ms+ 才能輸入）</td>
          <td>量 critical path、看 lazy chunk 是否要 preload</td>
      </tr>
      <tr>
          <td>即將寫 <code>observer.observe(document.body, { subtree: true })</code></td>
          <td>停 — 範圍過寬、補上限制</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼-reactive-效能要主動盤點">為什麼 reactive 效能要主動盤點</h2>
<p>Reactive 系統的成本不是線性 — 一個觸發頻率失控的 listener 會放大整個系統的負擔：</p>
<ul>
<li>一個 observer 觸發 → callback 執行 → DOM 變動 → 再觸發 observer → 無限迴圈</li>
<li>一個 input listener 沒 debounce → 每個鍵盤事件跑一次重 query → CPU 飆高</li>
<li>一個 setInterval polling 50ms → 永遠不停、即使頁面背景</li>
</ul>
<p>主動盤點 = 寫之前先估觸發頻率、寫之後用 <code>console.count</code> 驗證。事後 debug 比事前設計貴 10 倍。</p>
<hr>
<h2 id="風險面向-1listener-觸發頻率">風險面向 1：Listener 觸發頻率</h2>
<h3 id="mutationobserver-三維度">MutationObserver 三維度</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>預設</th>
          <th>過寬訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Root</td>
          <td>最窄（具體 element）</td>
          <td><code>document.body</code> / <code>document.documentElement</code></td>
      </tr>
      <tr>
          <td>Options</td>
          <td><code>{ childList: true }</code></td>
          <td><code>{ subtree: true, attributes: true, characterData: true }</code></td>
      </tr>
      <tr>
          <td>Debounce</td>
          <td>0ms 或微 microtask</td>
          <td>沒寫 debounce、callback 執行 &gt; 5ms</td>
      </tr>
  </tbody>
</table>
<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="c1">// 監聽整個 page 任何變動
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(</span><span class="nx">cb</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></span><span class="line"><span class="ln">3</span><span class="cl">  <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">4</span><span class="cl">  <span class="nx">subtree</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="nx">attributes</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nx">characterData</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="p">});</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1">// 一次 react state 變動 → 100+ 個 callback
</span></span></span></code></pre></div><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">root</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="kd">let</span> <span class="nx">timer</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">callback</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span>  <span class="c1">// debounce 100ms
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="p">}).</span><span class="nx">observe</span><span class="p">(</span><span class="nx">root</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">// 只監聽 results 直接子節點變動、debounce 100ms
</span></span></span></code></pre></div><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="kd">let</span> <span class="nx">count</span> <span class="o">=</span> <span class="mi">0</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="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">count</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;mutation&#39;</span><span class="p">,</span> <span class="nx">count</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}).</span><span class="nx">observe</span><span class="p">(...);</span>
</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"><span class="c1">// 預期：使用者打字 1 秒、觸發 10 次以下
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1">// 觀察：100+ 次 → 範圍過寬、加 debounce 或縮 root
</span></span></span></code></pre></div><p>或用 <code>console.count('decorate')</code> 計數、看每秒觸發幾次。</p>
<hr>
<h2 id="風險面向-2polling-換-observer">風險面向 2：Polling 換 Observer</h2>
<h3 id="反例setinterval-polling">反例：setInterval polling</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">timer</span> <span class="o">=</span> <span class="nx">setInterval</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">if</span> <span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">decorate</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nx">clearInterval</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">},</span> <span class="mi">50</span><span class="p">);</span></span></span></code></pre></div><p>問題：CPU 50% busy waiting、即使元素永遠不出現、interval 永遠跑。</p>
<h3 id="對例mutationobserver--fast-path">對例：MutationObserver + fast-path</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="kd">function</span> <span class="nx">waitForElement</span><span class="p">(</span><span class="nx">selector</span><span class="p">,</span> <span class="nx">root</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"> 2</span><span class="cl">  <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="nx">resolve</span> <span class="p">=&gt;</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">existing</span> <span class="o">=</span> <span class="nx">root</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="nx">selector</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">existing</span><span class="p">)</span> <span class="k">return</span> <span class="nx">resolve</span><span class="p">(</span><span class="nx">existing</span><span class="p">);</span>
</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 class="kr">const</span> <span class="nx">obs</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      <span class="kr">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nx">root</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="nx">selector</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="nx">obs</span><span class="p">.</span><span class="nx">disconnect</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="nx">resolve</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nx">obs</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nx">root</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></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Fast-path 先檢查（如果已經在 DOM 立即返回）、否則 observer 等元素出現。0 latency、0 idle CPU、元素出現立刻觸發。</p>
<hr>
<h2 id="風險面向-3iteration--regex-成本">風險面向 3：Iteration / Regex 成本</h2>
<h3 id="反例每筆結果跑重-regex">反例：每筆結果跑重 regex</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">results</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">pagefind</span><span class="p">.</span><span class="nx">search</span><span class="p">(</span><span class="nx">query</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">filtered</span> <span class="o">=</span> <span class="nx">results</span><span class="p">.</span><span class="nx">results</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="o">/</span><span class="nx">complex</span><span class="o">|</span><span class="nx">regex</span><span class="o">|</span><span class="nx">here</span><span class="o">/</span><span class="nx">i</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">excerpt</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// 500 筆 × regex test = 500 次 regex 編譯與執行
</span></span></span></code></pre></div><h3 id="對例regex-compile-一次用-cached-version">對例：regex compile 一次、用 cached version</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">re</span> <span class="o">=</span> <span class="sr">/complex|regex|here/i</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">filtered</span> <span class="o">=</span> <span class="nx">results</span><span class="p">.</span><span class="nx">results</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="nx">re</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">excerpt</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// regex 只編譯一次、test 每次便宜
</span></span></span></code></pre></div><h3 id="量-iteration-成本">量 iteration 成本</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="nx">console</span><span class="p">.</span><span class="nx">time</span><span class="p">(</span><span class="s1">&#39;filter&#39;</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">filtered</span> <span class="o">=</span> <span class="nx">results</span><span class="p">.</span><span class="nx">filter</span><span class="p">(...);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">timeEnd</span><span class="p">(</span><span class="s1">&#39;filter&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// 觀察：&gt; 16ms → 影響 60fps、要優化
</span></span></span></code></pre></div><h3 id="大資料量的常用優化">大資料量的常用優化</h3>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>優化</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>每筆都跑 regex</td>
          <td>regex 編譯一次、test 重用</td>
      </tr>
      <tr>
          <td>每筆 query DOM</td>
          <td>DOM query 一次、緩存結果</td>
      </tr>
      <tr>
          <td>排序 N²</td>
          <td>用 <code>Array.sort()</code> (N log N)</td>
      </tr>
      <tr>
          <td>全量過濾後分頁</td>
          <td>分頁邊界提前 break、不跑完全部</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="風險面向-4layout-reflow-成本">風險面向 4：Layout Reflow 成本</h2>
<p>Reflow（重新計算 layout） &gt; Repaint（重繪） &gt; Composite（合成）— 三者成本遞減。</p>
<h3 id="reflow-觸發訊號">Reflow 觸發訊號</h3>
<table>
  <thead>
      <tr>
          <th>操作</th>
          <th>成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>改 width / height / top / margin</td>
          <td>Reflow（layout 變動）</td>
      </tr>
      <tr>
          <td>改 color / background</td>
          <td>Repaint（不影響 layout）</td>
      </tr>
      <tr>
          <td>改 transform / opacity</td>
          <td>Composite（GPU、最便宜）</td>
      </tr>
      <tr>
          <td>讀 <code>getBoundingClientRect()</code></td>
          <td>強制 sync reflow（如果 pending 變動）</td>
      </tr>
  </tbody>
</table>
<h3 id="反例read-write-read-write-觸發-layout-thrashing">反例：read-write-read-write 觸發 layout thrashing</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="nx">elements</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">el</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">w</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetWidth</span><span class="p">;</span>       <span class="c1">// read
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">w</span> <span class="o">*</span> <span class="mi">2</span><span class="si">}</span><span class="sb">px`</span><span class="p">;</span>  <span class="c1">// write
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">h</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetHeight</span><span class="p">;</span>      <span class="c1">// read（強制 reflow）
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>  <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">h</span> <span class="o">*</span> <span class="mi">2</span><span class="si">}</span><span class="sb">px`</span><span class="p">;</span> <span class="c1">// write
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="p">});</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// 每次 read 觸發一次 reflow、N 個元素 = N 次 reflow
</span></span></span></code></pre></div><h3 id="對例批量-read批量-write">對例：批量 read、批量 write</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">sizes</span> <span class="o">=</span> <span class="nx">elements</span><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="p">({</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nx">el</span><span class="p">,</span> <span class="nx">w</span><span class="o">:</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetWidth</span><span class="p">,</span> <span class="nx">h</span><span class="o">:</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetHeight</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}));</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nx">sizes</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(({</span> <span class="nx">el</span><span class="p">,</span> <span class="nx">w</span><span class="p">,</span> <span class="nx">h</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">w</span> <span class="o">*</span> <span class="mi">2</span><span class="si">}</span><span class="sb">px`</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">h</span> <span class="o">*</span> <span class="mi">2</span><span class="si">}</span><span class="sb">px`</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="c1">// 1 次 reflow、性能提升 N 倍
</span></span></span></code></pre></div><h3 id="量-reflow-成本">量 reflow 成本</h3>
<p>Chrome DevTools Performance panel → 找 &ldquo;Layout&rdquo; 紫色塊。&gt; 16ms 要優化。</p>
<hr>
<h2 id="風險面向-5資源載入時序">風險面向 5：資源載入時序</h2>
<h3 id="critical-path-vs-lazy-chunk">Critical path vs lazy chunk</h3>
<table>
  <thead>
      <tr>
          <th>資源</th>
          <th>該不該 lazy</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>首屏需要的 CSS / JS</td>
          <td>否（critical path、preload）</td>
      </tr>
      <tr>
          <td>搜尋頁的 search index</td>
          <td>是（使用者進搜尋頁前不需要）</td>
      </tr>
      <tr>
          <td>Footer 圖片</td>
          <td>是（lazy load on scroll）</td>
      </tr>
      <tr>
          <td>跟首屏互動相關的 JS</td>
          <td>否（input listener 要立刻 ready）</td>
      </tr>
  </tbody>
</table>
<h3 id="範例搜尋頁的-lazy-chunk">範例：搜尋頁的 lazy chunk</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;!-- 搜尋頁進來時、preload 第一個 chunk --&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;preload&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/_pagefind/pagefind-entry.json&#34;</span> <span class="na">as</span><span class="o">=</span><span class="s">&#34;fetch&#34;</span> <span class="na">crossorigin</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">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;preload&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/_pagefind/pagefind.js&#34;</span> <span class="na">as</span><span class="o">=</span><span class="s">&#34;script&#34;</span><span class="p">&gt;</span>
</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"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;module&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="kr">import</span><span class="p">(</span><span class="s1">&#39;/_pagefind/pagefind.js&#39;</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">p</span> <span class="p">=&gt;</span> <span class="nx">p</span><span class="p">.</span><span class="nx">init</span><span class="p">());</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div><p>不 preload 的代價：使用者進搜尋頁 → 點 input → 等 200-500ms 才能搜尋。</p>
<h3 id="量-critical-path">量 critical path</h3>
<p>Chrome DevTools Network panel → 看每個資源的 timing。Slow 3G throttle 模擬真實使用者環境。</p>
<hr>
<h2 id="盤點-reactive-listener-的協議">盤點 reactive listener 的協議</h2>
<p>對複雜頁面（搜尋頁、dashboard）做一次性盤點：</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. 列出所有 observer / listener
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nx">mutationObservers</span><span class="o">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">observers</span><span class="p">,</span>  <span class="c1">// 自家紀錄
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span>  <span class="nx">resizeObservers</span><span class="o">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">resizeObservers</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="nx">inputListeners</span><span class="o">:</span> <span class="s1">&#39;...&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">});</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="c1">// 2. 加 console.count 在每個 callback
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">decorateCount</span> <span class="o">=</span> <span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">return</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">count</span><span class="p">(</span><span class="sb">`decorate </span><span class="si">${</span><span class="o">++</span><span class="nx">c</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span> <span class="p">};</span> <span class="p">})();</span>
</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 class="c1">// 3. 操作頁面 1 分鐘、看 console
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">// 4. 任何 callback 執行 &gt; 100 次/分鐘 → 評估是否需要 debounce / 縮範圍
</span></span></span></code></pre></div><p>定期盤點（每加新 observer 後）= 主動發現觸發頻率失控、不等使用者抱怨。</p>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1搜尋頁打字卡頓">範例 1：搜尋頁打字卡頓</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="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</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="c1">// 每個鍵盤事件都重 query 整個 results、重排版
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">expensiveQuery</span><span class="p">(</span><span class="nx">input</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">renderResults</span><span class="p">(</span><span class="nx">results</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><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="kd">let</span> <span class="nx">timer</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(()</span> <span class="p">=&gt;</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">results</span> <span class="o">=</span> <span class="nx">expensiveQuery</span><span class="p">(</span><span class="nx">input</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nx">renderResults</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="p">},</span> <span class="mi">200</span><span class="p">);</span>  <span class="c1">// debounce 200ms
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"></span><span class="p">});</span></span></span></code></pre></div><h3 id="範例-2等元素出現">範例 2：等元素出現</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="kr">const</span> <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setInterval</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="k">if</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="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">decorate</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">clearInterval</span><span class="p">(</span><span class="nx">timer</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="p">},</span> <span class="mi">100</span><span class="p">);</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="k">new</span> <span class="nx">MutationObserver</span><span class="p">((</span><span class="nx">mutations</span><span class="p">,</span> <span class="nx">obs</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="k">if</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="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">obs</span><span class="p">.</span><span class="nx">disconnect</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">decorate</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="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></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// 注意：subtree 只在「等元素出現」場景可接受、決完後 disconnect
</span></span></span></code></pre></div><hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>寫 reactive code 或 perf debug 時：</p>
<ul>
<li><input disabled="" type="checkbox"> MutationObserver root 是不是最窄能達成目標的 element？</li>
<li><input disabled="" type="checkbox"> options 是不是只開必要的（<code>childList</code> 預設、<code>subtree</code> 要有理由、<code>attributes</code> 不是預設）？</li>
<li><input disabled="" type="checkbox"> 重 callback 有沒有 debounce / throttle？</li>
<li><input disabled="" type="checkbox"> setInterval / setTimeout polling 能不能換成 MutationObserver？</li>
<li><input disabled="" type="checkbox"> iteration / regex 在大資料量下測過嗎？&gt; 16ms 要優化</li>
<li><input disabled="" type="checkbox"> 改 layout 屬性有沒有 batch read-write、避免 layout thrashing？</li>
<li><input disabled="" type="checkbox"> Lazy chunk 是 critical path 還是真的 lazy？</li>
</ul>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code>）：</p>
<ul>
<li><a href="/blog/report/mutation-observer-scope/" data-link-title="MutationObserver 範圍與觸發頻率：監聽最少必要的變動" data-link-desc="MutationObserver 是非同步監聽工具、跟同步 selector 是不同議題。範圍寬會頻繁觸發、option 勾多會在不關心的變動上跑邏輯、apply 自己改 DOM 會觸發無限循環。本文是 observer 設計的完整指引。">mutation-observer-scope</a> — MutationObserver 範圍與觸發頻率</li>
<li><a href="/blog/report/mutationobserver-over-polling/" data-link-title="setTimeout 輪詢換 MutationObserver" data-link-desc="等元素出現的場景、用 MutationObserver 監聽 DOM 變化、看到目標就 disconnect — 沒延遲、CPU 不被輪詢吃。本文展開兩種等待機制的差異。">mutationobserver-over-polling</a> — setTimeout 輪詢換 MutationObserver</li>
<li><a href="/blog/report/reactive-listener-frequency-management/" data-link-title="Reactive 監聽器的效能 audit：跨 listener 類型盤點觸發頻率" data-link-desc="MutationObserver / ResizeObserver / event listener 各自的觸發頻率怎麼盤點。本文是效能 audit 視角 — 找問題用、跟 #29 (observer 設計指引) 互補不重複。">reactive-listener-frequency-management</a> — Reactive 監聽器的效能 audit</li>
<li><a href="/blog/report/runtime-iteration-and-regex-cost/" data-link-title="Runtime 計算成本：每筆迭代與正則" data-link-desc="Scope filter 對每筆結果跑 regex — 結果數量大時成為 frame budget 的主要消耗。本文盤點此類「每筆迭代 &#43; per-item 計算」的風險點與評估方法。">runtime-iteration-and-regex-cost</a> — Runtime 計算成本：每筆迭代與正則</li>
<li><a href="/blog/report/layout-reflow-measurement/" data-link-title="Layout reflow / repaint 的可量化評估" data-link-desc="Filter slot 切換、CSS 變數寫入、絕對定位重算 — 哪些操作觸發 reflow 而非僅 repaint、用什麼工具量、評估值落在哪個區間值得優化。">layout-reflow-measurement</a> — Layout reflow / repaint 的可量化評估</li>
<li><a href="/blog/report/lazy-loading-and-critical-path/" data-link-title="資源載入時序：lazy chunk 與 critical path" data-link-desc="Pagefind 的 index 採 chunked lazy load — 首次互動延遲與 critical path 之間的取捨怎麼盤點。預載 entry chunk 的時機與不預載的代價。">lazy-loading-and-critical-path</a> — 資源載入時序：lazy chunk 與 critical path</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><item><title>Managing Article Collections — 跨多篇相關文章的結構設計</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/managing-article-collections/</link><pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/managing-article-collections/</guid><description>&lt;p>本文件處理「一個主題累積多篇相關文章後、如何維持可導航、可組合、不發散」的情境。適用於開發檢討集（report folder）、系列 blog post、技術知識庫、產品文件群。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：本文件不依賴其他 reference。讀完即可獨立判斷一個 article collection 該怎麼結構化。&lt;/p>
&lt;p>&lt;strong>與 writing-articles.md 的分工&lt;/strong>：那篇處理「單篇文章內部如何寫」、本篇處理「多篇文章之間如何組合」。兩篇互補、各管不同 scale。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="適用範圍">適用範圍&lt;/h2>
&lt;p>當以下任一條件成立、考慮讀本檔：&lt;/p>
&lt;ul>
&lt;li>同一主題下已有 ≥ 5 篇文章、且預期還會增加&lt;/li>
&lt;li>多篇文章之間發現「重複的概念、重複的範例、重複的引用」&lt;/li>
&lt;li>索引（MOC / TOC）變得龐大、超過 200 行仍無法路由清楚&lt;/li>
&lt;li>寫第 N 篇時發現「我這條原則已經在前幾篇講過了、但讀者每次都要重讀」&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="三層結構">三層結構&lt;/h2>
&lt;p>跨多篇的 article collection 自然呈現三層、每層職責不同。寫作前先判斷你要寫的篇章屬於哪一層、避免層級混淆。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層&lt;/th>
 &lt;th>角色&lt;/th>
 &lt;th>焦點&lt;/th>
 &lt;th>例（report folder）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>抽象原則&lt;/strong>&lt;/td>
 &lt;td>跨情境共用的元規則&lt;/td>
 &lt;td>「這條原則為什麼成立」&lt;/td>
 &lt;td>#42 2 次門檻&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>情境檢討&lt;/strong>&lt;/td>
 &lt;td>「這次任務該怎麼做」&lt;/td>
 &lt;td>「這次的取捨選了什麼、付了什麼代價」&lt;/td>
 &lt;td>#14 Selector 精準度&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Pattern 卡片&lt;/strong>&lt;/td>
 &lt;td>「某做法什麼時候用」&lt;/td>
 &lt;td>「這個 pattern 的適用邊界與實作細節」&lt;/td>
 &lt;td>#46 Document 全文件 query&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="各層的內容邊界">各層的內容邊界&lt;/h3>
&lt;p>&lt;strong>抽象原則層&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>不寫具體 case（具體 case 在情境檢討層）&lt;/li>
&lt;li>寫「機制 / 為什麼這條規則成立 / 跨情境如何辨識」&lt;/li>
&lt;li>結尾列出對應的情境檢討篇、各自示範這條原則的哪個面向&lt;/li>
&lt;li>不寫「跟其他原則的關係」（那是 MOC 的責任）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>情境檢討層&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>寫「這次的具體任務、當時的限制、選了什麼、為什麼」&lt;/li>
&lt;li>用機會成本語氣描述方案（A/B/C/D 多選項、不用「正確 vs 不足」）&lt;/li>
&lt;li>取捨段落中可引用 Pattern 卡片&lt;/li>
&lt;li>結尾可援引抽象原則層、但不重述&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Pattern 卡片層&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>寫「這個 pattern 的核心動作、適合 / 不適合的情境、設計細節」&lt;/li>
&lt;li>結尾列出「跟其他做法的取捨」（橫向比較同維度的其他 pattern）&lt;/li>
&lt;li>不寫該 pattern 在某個具體任務的應用（那是情境檢討層）&lt;/li>
&lt;li>卡片可被多篇情境檢討篇共同引用&lt;/li>
&lt;/ul>
&lt;h3 id="層級混淆的失敗模式">層級混淆的失敗模式&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>抽象原則寫具體 case&lt;/td>
 &lt;td>變成又一篇情境檢討、跟對應實作篇重複&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>情境檢討寫成 Pattern 卡片&lt;/td>
 &lt;td>變成「這個做法的完整指引」、失去「這次任務」脈絡&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pattern 卡片寫成情境檢討&lt;/td>
 &lt;td>變成「這次我用 X 做了 Y」、失去 pattern 跨情境可重用性&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>寫之前自問：「這篇是給『遇到同類問題的讀者』看、還是給『要套用某個 pattern 的讀者』看、還是給『要學一條跨情境原則的讀者』看？」答案決定該寫哪一層。&lt;/p>
&lt;hr>
&lt;h2 id="抽象層辨識訊號">抽象層辨識訊號&lt;/h2>
&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>多篇實作隱含同一原則但都沒寫出來&lt;/td>
 &lt;td>抽出抽象層卡片&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>寫第三篇 X 類情境時、自我意識「這條原則我已經講過三次」&lt;/td>
 &lt;td>抽出抽象層卡片、之後三篇引用即可&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>三篇實作篇都用「2 次門檻」當隱含假設&lt;/td>
 &lt;td>抽出抽象層卡片明寫&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>反訊號&lt;/strong>（不該抽抽象層）：&lt;/p>
&lt;ul>
&lt;li>只有兩篇看似相關 — 證據不足、可能只是巧合相似&lt;/li>
&lt;li>抽出來後讀者沒有獨立 grep 該抽象層的需求&lt;/li>
&lt;li>抽象層內容會跟某篇實作篇高度重疊（表示原則跟情境綁太緊、抽不出真正的抽象）&lt;/li>
&lt;/ul>
&lt;p>抽象層是&lt;strong>精煉&lt;/strong>、不是分類 — 必須有獨立的「機制 / 為什麼」可寫、不只是把幾篇實作篇的標題列在一起。&lt;/p>
&lt;hr>
&lt;h2 id="pattern-卡片辨識訊號">Pattern 卡片辨識訊號&lt;/h2>
&lt;p>Pattern 卡片從情境檢討的「設計取捨段落」中抽出。判讀訊號：&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>取捨段落某選項的內容超過 3-4 個段落&lt;/td>
 &lt;td>評估抽 Pattern 卡片&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>寫到「這個做法在 #N 也有用到」第二次&lt;/td>
 &lt;td>抽 Pattern 卡片、之後多篇共用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>該選項有獨立的失敗模式 / 設計細節 / 應用範例&lt;/td>
 &lt;td>適合抽&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>取捨段落變成「我先把這個 pattern 完整說完、再講其他選項」&lt;/td>
 &lt;td>該抽、不然取捨段落失衡&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>反訊號&lt;/strong>（不該抽 Pattern 卡片）：&lt;/p>
&lt;ul>
&lt;li>該選項只在「容忍度」這類單一參數上跟其他選項不同 — 留 inline、抽出收益低&lt;/li>
&lt;li>該選項是反模式、沒有獨立應用情境 — 留 inline 當對比&lt;/li>
&lt;li>該選項只在這一篇出現、未來不太可能被其他篇引用 — 留 inline&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>核心判準&lt;/strong>：抽出後是否多篇受惠？只有一篇用到、抽出純粹是 fragmentation。&lt;/p>
&lt;hr>
&lt;h2 id="素材庫與情境比例設計">素材庫與情境比例設計&lt;/h2>
&lt;p>跨多篇文章需要同時管理「讀者會看到的情境」與「作者用來支撐情境的素材」。文章情境負責教讀者推演，素材庫負責支撐反向驗證、壓力變體與後續擴寫；兩者的數量不必相同。&lt;/p>
&lt;h3 id="比例原則">比例原則&lt;/h3>
&lt;p>當一組文章要靠真實案例、研究來源或事件材料支撐時，採「少情境、多素材」比例。文章只選 4-5 個主情境，素材庫保留約 2-3 倍的 field cases 或 source cards。&lt;/p></description><content:encoded><![CDATA[<p>本文件處理「一個主題累積多篇相關文章後、如何維持可導航、可組合、不發散」的情境。適用於開發檢討集（report folder）、系列 blog post、技術知識庫、產品文件群。</p>
<blockquote>
<p><strong>自包含聲明</strong>：本文件不依賴其他 reference。讀完即可獨立判斷一個 article collection 該怎麼結構化。</p>
<p><strong>與 writing-articles.md 的分工</strong>：那篇處理「單篇文章內部如何寫」、本篇處理「多篇文章之間如何組合」。兩篇互補、各管不同 scale。</p></blockquote>
<hr>
<h2 id="適用範圍">適用範圍</h2>
<p>當以下任一條件成立、考慮讀本檔：</p>
<ul>
<li>同一主題下已有 ≥ 5 篇文章、且預期還會增加</li>
<li>多篇文章之間發現「重複的概念、重複的範例、重複的引用」</li>
<li>索引（MOC / TOC）變得龐大、超過 200 行仍無法路由清楚</li>
<li>寫第 N 篇時發現「我這條原則已經在前幾篇講過了、但讀者每次都要重讀」</li>
</ul>
<hr>
<h2 id="三層結構">三層結構</h2>
<p>跨多篇的 article collection 自然呈現三層、每層職責不同。寫作前先判斷你要寫的篇章屬於哪一層、避免層級混淆。</p>
<table>
  <thead>
      <tr>
          <th>層</th>
          <th>角色</th>
          <th>焦點</th>
          <th>例（report folder）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>抽象原則</strong></td>
          <td>跨情境共用的元規則</td>
          <td>「這條原則為什麼成立」</td>
          <td>#42 2 次門檻</td>
      </tr>
      <tr>
          <td><strong>情境檢討</strong></td>
          <td>「這次任務該怎麼做」</td>
          <td>「這次的取捨選了什麼、付了什麼代價」</td>
          <td>#14 Selector 精準度</td>
      </tr>
      <tr>
          <td><strong>Pattern 卡片</strong></td>
          <td>「某做法什麼時候用」</td>
          <td>「這個 pattern 的適用邊界與實作細節」</td>
          <td>#46 Document 全文件 query</td>
      </tr>
  </tbody>
</table>
<h3 id="各層的內容邊界">各層的內容邊界</h3>
<p><strong>抽象原則層</strong>：</p>
<ul>
<li>不寫具體 case（具體 case 在情境檢討層）</li>
<li>寫「機制 / 為什麼這條規則成立 / 跨情境如何辨識」</li>
<li>結尾列出對應的情境檢討篇、各自示範這條原則的哪個面向</li>
<li>不寫「跟其他原則的關係」（那是 MOC 的責任）</li>
</ul>
<p><strong>情境檢討層</strong>：</p>
<ul>
<li>寫「這次的具體任務、當時的限制、選了什麼、為什麼」</li>
<li>用機會成本語氣描述方案（A/B/C/D 多選項、不用「正確 vs 不足」）</li>
<li>取捨段落中可引用 Pattern 卡片</li>
<li>結尾可援引抽象原則層、但不重述</li>
</ul>
<p><strong>Pattern 卡片層</strong>：</p>
<ul>
<li>寫「這個 pattern 的核心動作、適合 / 不適合的情境、設計細節」</li>
<li>結尾列出「跟其他做法的取捨」（橫向比較同維度的其他 pattern）</li>
<li>不寫該 pattern 在某個具體任務的應用（那是情境檢討層）</li>
<li>卡片可被多篇情境檢討篇共同引用</li>
</ul>
<h3 id="層級混淆的失敗模式">層級混淆的失敗模式</h3>
<table>
  <thead>
      <tr>
          <th>失敗模式</th>
          <th>表現</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>抽象原則寫具體 case</td>
          <td>變成又一篇情境檢討、跟對應實作篇重複</td>
      </tr>
      <tr>
          <td>情境檢討寫成 Pattern 卡片</td>
          <td>變成「這個做法的完整指引」、失去「這次任務」脈絡</td>
      </tr>
      <tr>
          <td>Pattern 卡片寫成情境檢討</td>
          <td>變成「這次我用 X 做了 Y」、失去 pattern 跨情境可重用性</td>
      </tr>
  </tbody>
</table>
<p>寫之前自問：「這篇是給『遇到同類問題的讀者』看、還是給『要套用某個 pattern 的讀者』看、還是給『要學一條跨情境原則的讀者』看？」答案決定該寫哪一層。</p>
<hr>
<h2 id="抽象層辨識訊號">抽象層辨識訊號</h2>
<p>抽象層卡片不是隨意添加的、有明確觸發訊號：</p>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>行動</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>多篇實作隱含同一原則但都沒寫出來</td>
          <td>抽出抽象層卡片</td>
      </tr>
      <tr>
          <td>寫第三篇 X 類情境時、自我意識「這條原則我已經講過三次」</td>
          <td>抽出抽象層卡片、之後三篇引用即可</td>
      </tr>
      <tr>
          <td>三篇實作篇都用「2 次門檻」當隱含假設</td>
          <td>抽出抽象層卡片明寫</td>
      </tr>
  </tbody>
</table>
<p><strong>反訊號</strong>（不該抽抽象層）：</p>
<ul>
<li>只有兩篇看似相關 — 證據不足、可能只是巧合相似</li>
<li>抽出來後讀者沒有獨立 grep 該抽象層的需求</li>
<li>抽象層內容會跟某篇實作篇高度重疊（表示原則跟情境綁太緊、抽不出真正的抽象）</li>
</ul>
<p>抽象層是<strong>精煉</strong>、不是分類 — 必須有獨立的「機制 / 為什麼」可寫、不只是把幾篇實作篇的標題列在一起。</p>
<hr>
<h2 id="pattern-卡片辨識訊號">Pattern 卡片辨識訊號</h2>
<p>Pattern 卡片從情境檢討的「設計取捨段落」中抽出。判讀訊號：</p>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>行動</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>取捨段落某選項的內容超過 3-4 個段落</td>
          <td>評估抽 Pattern 卡片</td>
      </tr>
      <tr>
          <td>寫到「這個做法在 #N 也有用到」第二次</td>
          <td>抽 Pattern 卡片、之後多篇共用</td>
      </tr>
      <tr>
          <td>該選項有獨立的失敗模式 / 設計細節 / 應用範例</td>
          <td>適合抽</td>
      </tr>
      <tr>
          <td>取捨段落變成「我先把這個 pattern 完整說完、再講其他選項」</td>
          <td>該抽、不然取捨段落失衡</td>
      </tr>
  </tbody>
</table>
<p><strong>反訊號</strong>（不該抽 Pattern 卡片）：</p>
<ul>
<li>該選項只在「容忍度」這類單一參數上跟其他選項不同 — 留 inline、抽出收益低</li>
<li>該選項是反模式、沒有獨立應用情境 — 留 inline 當對比</li>
<li>該選項只在這一篇出現、未來不太可能被其他篇引用 — 留 inline</li>
</ul>
<p><strong>核心判準</strong>：抽出後是否多篇受惠？只有一篇用到、抽出純粹是 fragmentation。</p>
<hr>
<h2 id="素材庫與情境比例設計">素材庫與情境比例設計</h2>
<p>跨多篇文章需要同時管理「讀者會看到的情境」與「作者用來支撐情境的素材」。文章情境負責教讀者推演，素材庫負責支撐反向驗證、壓力變體與後續擴寫；兩者的數量不必相同。</p>
<h3 id="比例原則">比例原則</h3>
<p>當一組文章要靠真實案例、研究來源或事件材料支撐時，採「少情境、多素材」比例。文章只選 4-5 個主情境，素材庫保留約 2-3 倍的 field cases 或 source cards。</p>
<table>
  <thead>
      <tr>
          <th>層</th>
          <th>建議數量</th>
          <th>責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>主文章情境</td>
          <td>4-5 個</td>
          <td>讓讀者看完後能實際演練或套用</td>
      </tr>
      <tr>
          <td>Field cases / source cards</td>
          <td>10-12 張</td>
          <td>支撐情境、反向驗證、補壓力變體</td>
      </tr>
      <tr>
          <td>Scenario cards</td>
          <td>4-5 張</td>
          <td>把多個來源轉成可重播的中性情境</td>
      </tr>
      <tr>
          <td>Pattern cards</td>
          <td>5-7 張</td>
          <td>抽出跨情境共用的做法與判讀欄位</td>
      </tr>
  </tbody>
</table>
<p>比例的核心是讓每個主情境背後至少有 2-3 個來源可支撐。這樣文章能保持可讀，素材庫也能提供足夠的反例、變體與後續延伸材料。</p>
<h3 id="source-first-規則">Source-first 規則</h3>
<p>Field case 的責任是保存可回溯材料。案例型素材先找來源，再抽出觀察、壓力、控制缺口、判讀訊號與可轉譯情境。</p>
<p>Scenario card 的責任是把來源轉譯成可演練情境。情境可以把多個來源合成中性服務壓力，但每個主要壓力點都要能回查到 field case 或 source card。</p>
<p>Pattern card 的責任是歸納。模式可以比單一案例更抽象，但要保留支撐來源、適用邊界、判讀訊號與下一步路由。</p>
<h3 id="何時補素材庫">何時補素材庫</h3>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>行動</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>每個 scenario 只靠 1 個來源支撐</td>
          <td>先補 field cases，再改文章</td>
      </tr>
      <tr>
          <td>情境看起來合理但缺少真實壓力</td>
          <td>查來源，補 defender / operator pressure</td>
      </tr>
      <tr>
          <td>文章只能展示 4-5 個情境，但後續還要擴寫</td>
          <td>素材庫維持 2-3 倍來源量</td>
      </tr>
      <tr>
          <td>Pattern card 只有單一案例支撐</td>
          <td>補第二個來源或降低抽象層級</td>
      </tr>
  </tbody>
</table>
<h3 id="何時停止補素材">何時停止補素材</h3>
<p>素材庫達到「每個主情境 2-3 個來源、每個 pattern 至少 1-2 個支撐案例」後，就先停止擴充。繼續補素材的收益會下降，下一步應轉向寫文章、跑 review，或把素材回寫到 MOC。</p>
<hr>
<h2 id="mocmap-of-content設計">MOC（Map of Content）設計</h2>
<p>跨多篇 collection 的入口（collection index / README / TOC）是 MOC、不是內容。</p>
<h3 id="入口的職責邊界">入口的職責邊界</h3>
<table>
  <thead>
      <tr>
          <th>職責</th>
          <th>是否屬於 MOC</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>列出所有篇、各一行 hook</td>
          <td>是</td>
      </tr>
      <tr>
          <td>按情境分組（路徑 / 場景導讀）</td>
          <td>是</td>
      </tr>
      <tr>
          <td>解釋這個 collection 是什麼</td>
          <td>是（簡短）</td>
      </tr>
      <tr>
          <td>重述各篇的「概要」「outline」</td>
          <td><strong>否</strong>（各篇本身已存在、是冗餘）</td>
      </tr>
      <tr>
          <td>寫實質內容（症狀對應位置表）</td>
          <td><strong>否</strong>（屬於某篇文章內、抽出獨立篇）</td>
      </tr>
  </tbody>
</table>
<h3 id="每條索引條目格式">每條索引條目格式</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">- [<span class="nt">#N 標題</span>](<span class="na">slug/</span>) — 一句話 hook（≤ 150 字）</span></span></code></pre></div><p>關鍵約束：</p>
<ul>
<li>一行為限、不超過 150 字</li>
<li>Hook 講「這篇講什麼問題」、不重述「該怎麼做」</li>
<li>連結用 slug、不用標題（grep 友善）</li>
</ul>
<h3 id="場景導讀">場景導讀</h3>
<p>按任務情境列出多篇路徑、是 MOC 的合理擴展：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 路徑 N：[任務情境]
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span><span class="sb">`#A 標題`</span> → <span class="sb">`#B 標題`</span> → <span class="sb">`#C 標題`</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></span></code></pre></div><p>每條路徑長度 3-5 篇、長度超過表示「這條情境本身需要一篇 master」。</p>
<h3 id="moc-該避免的內容">MOC 該避免的內容</h3>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>為什麼避免</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>大綱式內容（每篇下面寫「涵蓋情境」+「理想做法」）</td>
          <td>跟各篇本身重複、各篇自己已寫</td>
      </tr>
      <tr>
          <td>「待補完」狀態標記長期不更新</td>
          <td>索引變成 staleness 來源、誤導讀者</td>
      </tr>
      <tr>
          <td>多層巢狀（「這個是 X、X 包含 A B C、A 包含 a1 a2」）</td>
          <td>讀者迷失導航、超過引用一層深</td>
      </tr>
      <tr>
          <td>各篇的判讀徵兆都列在 MOC</td>
          <td>那是文章內容、MOC 只給入口</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="跨篇引用-protocol">跨篇引用 protocol</h2>
<p>多篇 collection 內互相引用是常態、但要有紀律：</p>
<h3 id="引用要說明為什麼">引用要說明「為什麼」</h3>
<p><strong>好的引用</strong>：</p>
<blockquote>
<p>具體做法（<code>@import url(...) layer(...)</code>）與升級兼容性、其他外部組件的 layer 策略，由 [#24 CSS Layers 取代 specificity 戰] 完整展開。在邊界辨識上、本篇要記住的是：遇到 specificity 30+ 的覆寫戰、改去看 layer 維度。</p></blockquote>
<p><strong>不好的引用</strong>：</p>
<blockquote>
<p>另見 #24。</p></blockquote>
<p>差別是：好引用告訴讀者「為什麼引這個、引去看什麼」、不好引用要讀者自己猜。</p>
<h3 id="引用最多一層深">引用最多一層深</h3>
<p>A 引 B、B 引 C — 讀者從 A 出發、要看完 A + B + C 才能理解、認知負擔過大。</p>
<p>修正方式：</p>
<ul>
<li>把 C 的關鍵點濃縮進 B、B 自包含</li>
<li>或把 B 從引用鏈中移除、A 直接連 C</li>
</ul>
<h3 id="引用方向避免循環">引用方向避免循環</h3>
<p>A 引 B、B 也引 A — 讀者在兩篇之間來回跳、永遠不知道哪篇是「主」。</p>
<p>通常這表示<strong>抽象層缺漏</strong> — A 跟 B 都在援引一個沒寫出來的共同概念、那個概念該抽成抽象層。</p>
<h3 id="引用-idiom-庫">引用 idiom 庫</h3>
<p>從實際 corpus 累積的引用句型、各有適用情境。讀者看到這些句型就知道引用的責任分工：</p>
<h4 id="idiom-1抽象--情境情境檢討引用抽象層">Idiom 1：抽象 ← 情境（情境檢討引用抽象層）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">&gt; 本篇是 [<span class="nt">#42 2 次門檻</span>](<span class="na">path/</span>) 抽象原則在「驗證工具切換」這個面向的應用。</span></span></code></pre></div><ul>
<li><strong>位置</strong>：情境檢討文章開頭、核心原則段落之後</li>
<li><strong>效果</strong>：讀者一眼知道「這篇是某抽象原則的具體應用」、可以選擇先讀抽象層理解 mechanism、或直接讀本篇學具體做法</li>
<li><strong>適用</strong>：情境檢討文章、有對應的抽象層原則時</li>
</ul>
<h4 id="idiom-2責任分工拆分後篇章互相聲明範圍">Idiom 2：責任分工（拆分後篇章互相聲明範圍）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">&gt; </span><span class="ge">本篇焦點：客製 UI 該放哪。
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="ge"></span>&gt; - **framework 元件本身需要動時的安全規則**由 [<span class="nt">#13 JS 操作 framework 元件</span>](<span class="na">path/</span>) 處理</span></span></code></pre></div><ul>
<li><strong>位置</strong>：核心原則段落之後、為什麼段落之前</li>
<li><strong>效果</strong>：明確劃分「本篇 vs 相關篇」的責任邊界、避免讀者期待錯</li>
<li><strong>適用</strong>：拆分過的篇章群（例如 #5/#13 拆分後互相聲明）</li>
</ul>
<h4 id="idiom-3內聯引用段落內援引另一篇處理細節">Idiom 3：內聯引用（段落內援引另一篇處理細節）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">具體做法（<span class="sb">`@import url(...) layer(...)`</span>）與升級兼容性、其他外部組件的
</span></span><span class="line"><span class="ln">2</span><span class="cl">layer 策略、由 [<span class="nt">#24 CSS Layers</span>](<span class="na">path/</span>) 完整展開。在邊界辨識上、
</span></span><span class="line"><span class="ln">3</span><span class="cl">本篇要記住的是：遇到 specificity 30+ 的覆寫戰、改去看 layer 維度。</span></span></code></pre></div><ul>
<li><strong>位置</strong>：段落正文中、講到引用篇主題的地方</li>
<li><strong>效果</strong>：讀者拿到「為什麼引、引去看什麼、本篇仍要記住什麼」三件資訊</li>
<li><strong>適用</strong>：段落主題跟引用篇高度重疊、不適合在本篇重述</li>
</ul>
<h4 id="idiom-4moc-連結索引條目">Idiom 4：MOC 連結（索引條目）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">- [<span class="nt">#42 2 次門檻：第一次是運氣、第二次是訊號</span>](<span class="na">path/</span>) — 串 <span class="ni">#11</span> / <span class="ni">#15</span> / <span class="ni">#20</span> / <span class="ni">#23</span>、跨工具/測試/思路/溝通四面向</span></span></code></pre></div><ul>
<li><strong>位置</strong>：collection index / MOC</li>
<li><strong>效果</strong>：標題 + 一句話 hook、讓讀者選擇要不要進入該篇</li>
<li><strong>格式</strong>：<code>- [#N 標題](slug/) — hook（≤ 150 字）</code></li>
</ul>
<h4 id="idiom-5跨原則的關係表抽象層之間互相對位">Idiom 5：跨原則的關係表（抽象層之間互相對位）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><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 class="nt">#43 最小必要範圍</span>](<span class="na">path/</span>) | 「最小必要範圍」聚焦「縮影響範圍」、本篇聚焦「縮值來源數」、兩者都是「讓行為可預測」的不同面向 |</span></span></code></pre></div><ul>
<li><strong>位置</strong>：抽象層原則篇的「跟其他原則的關係」段落</li>
<li><strong>效果</strong>：讀者看到原則網的形狀、知道哪些原則可以一起援引</li>
</ul>
<h4 id="idiom-6對應的實作篇表抽象--情境--pattern">Idiom 6：對應的實作篇表（抽象 → 情境 / pattern）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><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 class="nt">#13 元件邊界與 JS 操作</span>](<span class="na">path/</span>) | JS 元件邊界 | 「我可以動什麼」契約 |
</span></span><span class="line"><span class="ln">4</span><span class="cl">| [<span class="nt">#14 Selector 精準度</span>](<span class="na">path/</span>) | DOM query 範圍 | 起點 / 範圍 / 過濾三維度 |</span></span></code></pre></div><ul>
<li><strong>位置</strong>：抽象層原則篇結尾</li>
<li><strong>效果</strong>：讀者依議題挑實作篇、不需要逐篇讀</li>
</ul>
<hr>
<h2 id="三層-structure-詳細對照">三層 structure 詳細對照</h2>
<p>跨多篇 collection 中、三類文章各有不同的 standard structure。對照表先給快速判斷依據、之後是各類完整模板。</p>
<h3 id="段落對照表">段落對照表</h3>
<table>
  <thead>
      <tr>
          <th>段落</th>
          <th>情境檢討</th>
          <th>抽象層原則</th>
          <th>Pattern 卡片</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>核心原則 / 核心做法</td>
          <td>有</td>
          <td>有</td>
          <td>有</td>
      </tr>
      <tr>
          <td>為什麼（商業邏輯 / 機制）</td>
          <td>有</td>
          <td>有</td>
          <td>有</td>
      </tr>
      <tr>
          <td>這次任務情境</td>
          <td>有</td>
          <td>無</td>
          <td>無</td>
      </tr>
      <tr>
          <td>設計取捨 A/B/C/D</td>
          <td>有</td>
          <td>無</td>
          <td>無</td>
      </tr>
      <tr>
          <td>跨情境的應用面向</td>
          <td>無</td>
          <td>有</td>
          <td>無</td>
      </tr>
      <tr>
          <td>適合 / 不適合</td>
          <td>（在設計取捨內）</td>
          <td>有（適用邊界）</td>
          <td>有</td>
      </tr>
      <tr>
          <td>設計細節</td>
          <td>（在執行段內）</td>
          <td>無</td>
          <td>有</td>
      </tr>
      <tr>
          <td>跟其他做法 / 原則的關係</td>
          <td>有，簡短</td>
          <td>有，明文</td>
          <td>有，橫向對照</td>
      </tr>
      <tr>
          <td>對應的實作篇</td>
          <td>無</td>
          <td>有</td>
          <td>無</td>
      </tr>
      <tr>
          <td>應用範例</td>
          <td>（在執行段內）</td>
          <td>無</td>
          <td>有</td>
      </tr>
      <tr>
          <td>判讀徵兆</td>
          <td>有</td>
          <td>有</td>
          <td>有</td>
      </tr>
  </tbody>
</table>
<p><strong>分類錯了、structure 也錯</strong>。寫文章前先判斷：</p>
<ul>
<li>「這篇是某次任務的紀錄嗎？」→ 情境檢討</li>
<li>「這篇是跨多情境的元規則嗎？」→ 抽象層原則</li>
<li>「這篇是某做法的深入指引嗎？」→ Pattern 卡片</li>
</ul>
<h3 id="情境檢討-structure-模板">情境檢討 structure 模板</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 核心原則
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></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="gu">## 為什麼 [這個議題重要]
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 商業邏輯
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### [可能的子議題]
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">## 這次任務的實際情境
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 觀察
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 判讀
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">### 執行
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">## 設計取捨：[維度]
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">### A：[做法]（這個專案的預設）
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu">### B：[替代]
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu">### C：[另一條路]
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu">### D：[極端 / 反模式]
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu">## 跟其他原則的關係（選用）
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">## 判讀徵兆</span></span></code></pre></div><h3 id="抽象層原則-structure-模板">抽象層原則 structure 模板</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 核心原則
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></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="gu">## 為什麼 [這條原則成立]
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### [機制：為什麼這條規則在多情境都成立]
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">## [這條原則的多個應用面向]
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">### 應用 1：[情境名]（→ 實作篇 #N）
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 應用 2：[情境名]（→ 實作篇 #M）
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 應用 3：...
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">## 不該套用 [本原則] 的情境
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span>[適用邊界]
</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 class="gu">## 跟其他原則的關係
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu"></span>[本原則跟其他抽象層原則的關係]
</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 class="gu">## 對應的實作篇
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu"></span>[一個表、列出每篇示範的面向]
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">## 判讀徵兆</span></span></code></pre></div><p>抽象層的取捨在情境邊界、不在方案選項。抽象層讀者面對的決定是「我這次的情境該不該套用這條原則」 — 答案在「適用 vs 不適用的情境邊界」、不在「四個並列方案中選一個」。所以抽象層的對應段落是「不該套用 X 的情境」（邊界釐清）、而非「設計取捨 A/B/C/D」（方案選擇）。</p>
<h3 id="pattern-卡片-structure-模板">Pattern 卡片 structure 模板</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 核心做法
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>[做法簡述、code 範例]
</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="gu">## 這個做法存在的價值
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu"></span>[為什麼這個做法獨立存在、什麼是它獨有的]
</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"><span class="gu">## 適合的情境
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></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="gu">## 不適合的情境
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span>[表格：情境 / 為什麼不夠 / 改用 <span class="ni">#N</span> 其他 pattern]
</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 class="gu">## 設計細節
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span>[實作的具體技巧、邊界 case 處理]
</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"><span class="gu">## 跟其他 pattern 的關係
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu"></span>[橫向比較同維度的其他 pattern、引用情境檢討的設計取捨段落]
</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 class="gu">## 應用範例
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu"></span>[具體 code 範例]
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">## 判讀徵兆</span></span></code></pre></div><p>Pattern 卡片的取捨在橫向對照、不在自家 A/B/C/D。Pattern 卡片本身是某情境檢討「設計取捨 A/B/C/D」中的一個選項（例如某「document query」就是「起點選擇」中的選項 A）。它的讀者面對的決定是「什麼時候用這個 pattern」 — 答案在「跟同維度其他 pattern 的橫向對照」、而非「自家內再分 A/B/C/D」。所以 Pattern 卡片的對應段落是「跟其他 pattern 的關係」（橫向）、而非「設計取捨 A/B/C/D」（縱向）。</p>
<h3 id="structure-分類錯誤的徵兆">structure 分類錯誤的徵兆</h3>
<table>
  <thead>
      <tr>
          <th>徵兆</th>
          <th>分類錯誤</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>抽象層原則寫了「這次任務的實際情境」</td>
          <td>寫成情境檢討 — 抽象層該講機制、不該講某次任務</td>
      </tr>
      <tr>
          <td>抽象層原則硬擠 A/B/C/D 設計取捨</td>
          <td>抽象層該談「適用邊界」、不是方案選項</td>
      </tr>
      <tr>
          <td>Pattern 卡片硬擠 A/B/C/D 設計取捨</td>
          <td>Pattern 卡片該用「跟其他 pattern 橫向對照」</td>
      </tr>
      <tr>
          <td>情境檢討跳過「設計取捨」段落</td>
          <td>情境檢討的核心就是記錄「這次選了什麼、為什麼」</td>
      </tr>
      <tr>
          <td>三類文章共用同一個段落 template</td>
          <td>每類讀者問題不同、template 也該不同</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="拆分判準focus-是議題完整度">拆分判準：focus 是議題完整度</h2>
<p>當不確定一篇該不該拆、判讀的核心問題是：「<strong>這篇文章聚焦在什麼問題？有沒有議題切了一半？</strong>」</p>
<h3 id="兩個常見誤判">兩個常見誤判</h3>
<table>
  <thead>
      <tr>
          <th>誤判</th>
          <th>為什麼錯</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「兩篇沒衝突 → 不需要拆」</td>
          <td>兩篇可能各切了一半同樣議題、誰都沒講完整</td>
      </tr>
      <tr>
          <td>「邊界很清晰 → 不需要拆」</td>
          <td>篇章內部仍可能塞了兩個獨立議題、傘式標題遮蓋發散</td>
      </tr>
  </tbody>
</table>
<h3 id="寫作--review-雙階段自問">寫作 / review 雙階段自問</h3>
<p>寫作時、寫完先自問三題：</p>
<ol>
<li>這篇聚焦的問題用一句話能說完嗎？</li>
<li>內文每段都服務這個問題嗎？</li>
<li>有沒有段落像是「順便提一下」的離題內容？</li>
</ol>
<p>任一題答否、檢視該段是否該拆 / 移除 / 補進其他篇。</p>
<p>review 時、讀完先問三題：</p>
<ol>
<li>這篇實際聚焦在什麼問題（<strong>不看標題、看內文</strong>判斷）？</li>
<li>標題與內文焦點對得起來嗎？</li>
<li>有沒有兩個獨立議題各佔半篇？</li>
</ol>
<p>兩階段共同特徵：<strong>都需要讀完內文、不能只看大綱</strong>。大綱式 review 看不出議題切了一半、看不出語氣絕對主義、看不出 focus 發散。</p>
<h3 id="議題切了一半的辨識訊號">議題切了一半的辨識訊號</h3>
<ul>
<li>標題用「+」「與」「以及」綁兩個獨立概念</li>
<li>內文有兩段獨立的「為什麼這個機制成立」、各對應一個概念</li>
<li>讀者通常一次只 grep 其中一個關鍵字</li>
<li>列出讀者最可能 grep 的 3 個關鍵字、發現一個關鍵字無法涵蓋全文</li>
<li>即使不衝突、仍該拆</li>
</ul>
<h3 id="反例-vs-正例">反例 vs 正例</h3>
<p><strong>反例</strong>：標題「Selector + Observer 精準度」、合在一篇</p>
<ul>
<li>雖然都用「精準度」這個傘綁、機制完全不同（同步查詢 vs 非同步監聽）</li>
<li>讀者來查「我的 selector 撈太多」只關心 selector 半邊</li>
<li>讀者來查「我的 observer 觸發太頻繁」只關心 observer 半邊</li>
<li>沒有人會兩個問題同時問</li>
</ul>
<p><strong>正例</strong>：拆成兩篇「Selector 精準度」+「Observer 範圍與觸發頻率」、各自完整深入。</p>
<h3 id="拆分後的次要判準">拆分後的次要判準</h3>
<p>議題完整度（focus）通過後、再看共用價值與獨立可讀性：</p>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>問題</th>
          <th>行動</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>共用價值</td>
          <td>拆出來的卡片會被多篇引用嗎？</td>
          <td>是 → 拆出成共用素材；否 → 留 inline</td>
      </tr>
      <tr>
          <td>獨立可讀性</td>
          <td>拆出的卡片自己能成立嗎？</td>
          <td>有獨立「機制 / 適用情境 / 失敗模式」可寫 → 能成立</td>
      </tr>
  </tbody>
</table>
<h3 id="不該當判準的偽指標">不該當判準的偽指標</h3>
<ul>
<li><strong>行數多寡</strong>：8KB 的文章可能 focus 緊、3KB 的文章可能議題切兩半</li>
<li><strong>篇章邊界是否清晰</strong>：兩篇沒衝突 ≠ 各自完整</li>
<li><strong>寫的時候是否方便</strong>：方便不是優先序</li>
</ul>
<h3 id="完成標準">完成標準</h3>
<p>寫完後讀者能用一句話說出「這篇在講什麼問題」、且這句話精確到「換成同義不同詞也能說清楚」。若讀者要用兩句話才能涵蓋、表示有兩個議題、該拆。</p>
<hr>
<h2 id="review-與重構訊號">review 與重構訊號</h2>
<p>定期回顧 collection、看下列訊號：</p>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>重構動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>多篇用同樣 case 當例子</td>
          <td>抽抽象層、各篇引用</td>
      </tr>
      <tr>
          <td>一篇有 5+ 個取捨段落</td>
          <td>評估抽 Pattern 卡片</td>
      </tr>
      <tr>
          <td>索引重複「概要」+「文章本身」</td>
          <td>索引瘦身</td>
      </tr>
      <tr>
          <td>標題與內文焦點不一致</td>
          <td>改名 / 重寫 / 拆篇</td>
      </tr>
      <tr>
          <td>寫新篇時發現「我這個 pattern 已經在 #N 寫過了」</td>
          <td>抽 Pattern 卡片、新篇引用</td>
      </tr>
      <tr>
          <td>大綱式 review 通過、實際讀內文發現問題</td>
          <td>review process 必須讀內文</td>
      </tr>
  </tbody>
</table>
<h3 id="大綱式-review-的限制">大綱式 review 的限制</h3>
<p>只看標題與大綱、無法判讀：</p>
<ul>
<li>文章內議題是否切了一半</li>
<li>語氣是否絕對主義</li>
<li>各段落是否服務同一個 focus</li>
<li>引用是否有實質說明</li>
</ul>
<p><strong>review 必須讀實際內文</strong> — 大綱只能看「topic 對不對」、不能看「議題完整度 / 語氣 / focus」。</p>
<hr>
<h2 id="自檢清單">自檢清單</h2>
<p>跨多篇 collection 提交前自檢：</p>
<ul>
<li><input disabled="" type="checkbox"> 三層結構就位（抽象原則 / 情境檢討 / Pattern 卡片各層該有的有）</li>
<li><input disabled="" type="checkbox"> 各篇的層級不混淆（抽象層不寫具體 case、情境層不寫成 pattern 完整指引）</li>
<li><input disabled="" type="checkbox"> MOC 入口只做路由、無大綱式冗餘</li>
<li><input disabled="" type="checkbox"> MOC 每條索引一行、不超過 150 字</li>
<li><input disabled="" type="checkbox"> 跨篇引用都說明了「為什麼引用、引去看什麼」</li>
<li><input disabled="" type="checkbox"> 沒有 A→B→C 多層跳躍引用</li>
<li><input disabled="" type="checkbox"> 沒有 A↔B 雙向循環引用（若有、抽抽象層）</li>
<li><input disabled="" type="checkbox"> 沒有議題切了一半的篇章（標題與內文焦點一致）</li>
<li><input disabled="" type="checkbox"> 機會成本語氣跨篇一致（沒有混用「正確概念」與「預設選擇」）</li>
<li><input disabled="" type="checkbox"> 「待補完」狀態標記都更新或移除</li>
</ul>
<hr>
<h2 id="與核心原則的映射">與核心原則的映射</h2>
<table>
  <thead>
      <tr>
          <th>本 reference 規則</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>Pattern 卡片辨識</td>
          <td>原子化 + 可重用性</td>
          <td>把可跨情境共用的做法抽成獨立素材</td>
      </tr>
      <tr>
          <td>素材庫比例設計</td>
          <td>索引建立 + 可重用性</td>
          <td>文章保留少數主情境、素材庫保留更多來源</td>
      </tr>
      <tr>
          <td>MOC 入口只做路由</td>
          <td>索引建立</td>
          <td>入口不承載細節、避免重複</td>
      </tr>
      <tr>
          <td>跨篇引用要說明為什麼</td>
          <td>意圖顯性</td>
          <td>引用本身要表達意圖、不只連結</td>
      </tr>
      <tr>
          <td>議題完整度優先</td>
          <td>原子化</td>
          <td>拆分依據是 focus、不是邊界</td>
      </tr>
      <tr>
          <td>review 讀內文不只大綱</td>
          <td>可查詢性 + 意圖顯性</td>
          <td>內文是 source of truth、大綱可能 stale</td>
      </tr>
  </tbody>
</table>
<hr>
<p><strong>Last Updated</strong>: 2026-04-30
<strong>Version</strong>: 0.4.0 — 補「素材庫與情境比例設計」：文章主情境維持 4-5 個、field/source 素材維持約 2-3 倍；新增 source-first、scenario 轉譯與 pattern 歸納規則，避免以自行生成情境取代可回溯素材
<strong>Version</strong>: 0.3.0 — 從 writing-articles.md 過載反思整合內容：「拆分判準（focus 是議題完整度）」完整搬入（含寫作 / review 自問三題、議題切一半的辨識訊號、反例 vs 正例、完成標準）+「三層 structure 詳細對照」展開為完整模板（情境 / 抽象 / Pattern 各一份）+「structure 分類錯誤的徵兆」清單。現在這份 reference 完整涵蓋「跨多篇相關文章的結構設計」、不需要回 writing-articles.md 找
<strong>Version</strong>: 0.2.0 — 從批量改寫 35 篇的經驗回流：補「跨篇引用 idiom 庫」（6 種句型 + 適用情境）+「三層 structure 對照」（quick reference、詳細在 writing-articles.md 規則九）
<strong>Version</strong>: 0.1.0 — 初版（從 report folder 累積 50+ 篇文章、整理跨篇結構心得）</p>
]]></content:encoded></item><item><title>Designing Fields — 欄位設計指引</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/designing-fields/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/designing-fields/</guid><description>&lt;p>本文件為「設計欄位」情境的完整指引。適用於 ticket 模板、YAML frontmatter、API response、database schema、配置檔案等任何&lt;strong>多欄位結構&lt;/strong>的設計。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>為什麼獨立成篇&lt;/strong>：欄位設計的錯誤會被模板放大。一個設計不良的 ticket 模板會產生上百個混淆 ticket、上千則語意空洞的資料。欄位一旦上線就難以撤回，因為後續所有資料都已按此格式寫入。&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。核心原則的精要在本文件內展開於「欄位設計」情境；需要跨情境套用時，才去讀對應的 &lt;code>writing-*.md&lt;/code>。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="tldr30-秒版本">TL;DR（30 秒版本）&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>每個欄位承載一個維度&lt;/strong>，不同欄位描述同一件事的不同面向（what 描述動作、why 陳述動機、acceptance 定義可驗證條件）。&lt;/li>
&lt;li>&lt;strong>frontmatter 欄位為程式化查詢服務&lt;/strong>，ID 格式、enum 值、布林命名要穩定可 grep。&lt;/li>
&lt;li>&lt;strong>欄位名稱暗示其問什麼問題&lt;/strong>（&lt;code>why&lt;/code> 問動機，&lt;code>how&lt;/code> 問策略，&lt;code>blockedBy&lt;/code> 問阻塞關係）。&lt;/li>
&lt;li>&lt;strong>欄位值格式一致&lt;/strong>，enum 有限集優於自由文字，複合值用穩定分隔符。&lt;/li>
&lt;li>&lt;strong>新增欄位前問七個問題&lt;/strong>（見「新增欄位的決策框架」章節），避免欄位膨脹。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="目錄">目錄&lt;/h2>
&lt;ol>
&lt;li>原子化 × 欄位：每個欄位承載一個維度&lt;/li>
&lt;li>索引 × 欄位：程式化查詢、ID 格式、frontmatter 設計&lt;/li>
&lt;li>意圖顯性 × 欄位：欄位名稱即提問&lt;/li>
&lt;li>可查詢性 × 欄位：值格式一致性、enum 命名&lt;/li>
&lt;li>欄位設計 meta：新增欄位的決策框架&lt;/li>
&lt;li>Ticket 六欄位角度解析（六欄位總表 + 詳細範例連結）&lt;/li>
&lt;li>非 ticket 情境：YAML 配置、API response&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="1-原子化--欄位每個欄位承載一個維度">1. 原子化 × 欄位：每個欄位承載一個維度&lt;/h2>
&lt;p>&lt;strong>原則&lt;/strong>：每個欄位只回答一個問題。若一個欄位同時承載兩個維度，填寫者會混淆，查詢者會誤讀。&lt;/p>
&lt;h3 id="判斷標準">判斷標準&lt;/h3>
&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>描述需要「和」連接&lt;/td>
 &lt;td>&lt;code>status: &amp;quot;in_progress 且 blocked by API&amp;quot;&lt;/code>&lt;/td>
 &lt;td>拆成 &lt;code>status&lt;/code> + &lt;code>blockedBy&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>同一欄位混用動作與動機&lt;/td>
 &lt;td>&lt;code>what: &amp;quot;修 bug 因為用戶回報崩潰&amp;quot;&lt;/code>&lt;/td>
 &lt;td>拆成 &lt;code>what&lt;/code>（修 bug）+ &lt;code>why&lt;/code>（用戶回報崩潰影響可用性）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>單欄位塞多個可查詢值&lt;/td>
 &lt;td>&lt;code>tags: &amp;quot;p0 security urgent&amp;quot;&lt;/code>&lt;/td>
 &lt;td>改為 &lt;code>priority: p0&lt;/code> + &lt;code>category: security&lt;/code> + &lt;code>urgency: high&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>用自由文字藏結構資料&lt;/td>
 &lt;td>&lt;code>notes: &amp;quot;owner=alice, due=2026-04-20&amp;quot;&lt;/code>&lt;/td>
 &lt;td>拆成 &lt;code>owner: alice&lt;/code> + &lt;code>due: 2026-04-20&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="反例一個欄位承載兩個維度">反例：一個欄位承載兩個維度&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c"># 錯誤：status 同時表達「進度」和「阻塞原因」&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">status&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;in_progress_waiting_for_api_team&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>問題：&lt;/p>
&lt;ul>
&lt;li>查詢「所有 in_progress 的 ticket」需要字串前綴比對，不穩定。&lt;/li>
&lt;li>改阻塞對象時，狀態字串也要改，污染查詢歷史。&lt;/li>
&lt;li>報表無法分別統計「進度分佈」和「阻塞分佈」。&lt;/li>
&lt;/ul>
&lt;h3 id="正確兩個維度各自一個欄位">正確：兩個維度各自一個欄位&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nt">status&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">in_progress&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">blockedBy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">team&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">api&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">reason&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;等待 /v2/users 端點上線&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每個欄位&lt;strong>只回答一個問題&lt;/strong>：&lt;code>status&lt;/code> 回答「目前在哪個階段」，&lt;code>blockedBy&lt;/code> 回答「被什麼擋住」。&lt;/p>
&lt;h3 id="原子化測試">原子化測試&lt;/h3>
&lt;p>拿一個欄位問三個問題：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>這個欄位回答什麼問題？&lt;/strong> — 若答案需要「和」連接兩個問題，必須拆分。&lt;/li>
&lt;li>&lt;strong>這個欄位的值能獨立變更嗎？&lt;/strong> — 若必須連動其他資訊，代表混在一起了。&lt;/li>
&lt;li>&lt;strong>能用此欄位單獨做統計/排序嗎？&lt;/strong> — 若不能，代表它混入了其他維度。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="2-索引--欄位程式化查詢id-格式frontmatter-設計">2. 索引 × 欄位：程式化查詢、ID 格式、frontmatter 設計&lt;/h2>
&lt;p>&lt;strong>原則&lt;/strong>：frontmatter 欄位存在的理由之一是&lt;strong>讓程式能查詢&lt;/strong>。人眼看不出結構，程式才看得出。所以 ID 格式、enum 值、布林欄位的命名要為「程式化查詢」服務。&lt;/p>
&lt;h3 id="id-格式設計">ID 格式設計&lt;/h3>
&lt;p>好的 ID 同時對人和程式友善。以下是穩定 ID 格式的檢查點：&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>分層結構可拆解&lt;/td>
 &lt;td>&lt;code>v1.2.0-W03-021.4&lt;/code>&lt;/td>
 &lt;td>&lt;code>ticket_74_sub7&lt;/code>&lt;/td>
 &lt;td>前者可用分隔符拆成版本/wave/序號/子序號&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>穩定分隔符&lt;/td>
 &lt;td>用 &lt;code>-&lt;/code> 分層、&lt;code>.&lt;/code> 分子層&lt;/td>
 &lt;td>混用 &lt;code>_&lt;/code>、&lt;code>-&lt;/code>、&lt;code> &lt;/code>&lt;/td>
 &lt;td>程式 regex 才能一致比對&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>固定位數（可選）&lt;/td>
 &lt;td>&lt;code>P001&lt;/code>、&lt;code>P002&lt;/code>&lt;/td>
 &lt;td>&lt;code>P1&lt;/code>、&lt;code>P10&lt;/code>、&lt;code>P100&lt;/code>&lt;/td>
 &lt;td>字典序排序才會等於數值序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>不含空白與特殊字元&lt;/td>
 &lt;td>&lt;code>prop-cache-cleanup&lt;/code>&lt;/td>
 &lt;td>&lt;code>prop cache cleanup!&lt;/code>&lt;/td>
 &lt;td>避免需要引號與跳脫&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可預測的命名空間&lt;/td>
 &lt;td>&lt;code>PROP-&lt;/code> / &lt;code>UC-&lt;/code> / &lt;code>SPEC-&lt;/code> 前綴&lt;/td>
 &lt;td>無前綴純數字&lt;/td>
 &lt;td>能靠前綴快速過濾類別&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="frontmatter-為查詢服務">frontmatter 為查詢服務&lt;/h3>
&lt;p>frontmatter 的目的是「讓特定查詢變快」、不是「把資訊塞進去」 — 因為光把資訊塞進去只是換個地方存、查詢仍然要 scan；要查詢變快、欄位要做特定設計（穩定鍵名、可索引的值類型、前綴命名空間）才能匹配查詢工具的索引機制。把目的搞錯會做出「資訊完整但查詢仍要全文掃」的 frontmatter、讀者得到結果但工具拿不到效益。&lt;/p></description><content:encoded><![CDATA[<p>本文件為「設計欄位」情境的完整指引。適用於 ticket 模板、YAML frontmatter、API response、database schema、配置檔案等任何<strong>多欄位結構</strong>的設計。</p>
<blockquote>
<p><strong>為什麼獨立成篇</strong>：欄位設計的錯誤會被模板放大。一個設計不良的 ticket 模板會產生上百個混淆 ticket、上千則語意空洞的資料。欄位一旦上線就難以撤回，因為後續所有資料都已按此格式寫入。</p></blockquote>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。核心原則的精要在本文件內展開於「欄位設計」情境；需要跨情境套用時，才去讀對應的 <code>writing-*.md</code>。</p></blockquote>
<hr>
<h2 id="tldr30-秒版本">TL;DR（30 秒版本）</h2>
<ol>
<li><strong>每個欄位承載一個維度</strong>，不同欄位描述同一件事的不同面向（what 描述動作、why 陳述動機、acceptance 定義可驗證條件）。</li>
<li><strong>frontmatter 欄位為程式化查詢服務</strong>，ID 格式、enum 值、布林命名要穩定可 grep。</li>
<li><strong>欄位名稱暗示其問什麼問題</strong>（<code>why</code> 問動機，<code>how</code> 問策略，<code>blockedBy</code> 問阻塞關係）。</li>
<li><strong>欄位值格式一致</strong>，enum 有限集優於自由文字，複合值用穩定分隔符。</li>
<li><strong>新增欄位前問七個問題</strong>（見「新增欄位的決策框架」章節），避免欄位膨脹。</li>
</ol>
<hr>
<h2 id="目錄">目錄</h2>
<ol>
<li>原子化 × 欄位：每個欄位承載一個維度</li>
<li>索引 × 欄位：程式化查詢、ID 格式、frontmatter 設計</li>
<li>意圖顯性 × 欄位：欄位名稱即提問</li>
<li>可查詢性 × 欄位：值格式一致性、enum 命名</li>
<li>欄位設計 meta：新增欄位的決策框架</li>
<li>Ticket 六欄位角度解析（六欄位總表 + 詳細範例連結）</li>
<li>非 ticket 情境：YAML 配置、API response</li>
</ol>
<hr>
<h2 id="1-原子化--欄位每個欄位承載一個維度">1. 原子化 × 欄位：每個欄位承載一個維度</h2>
<p><strong>原則</strong>：每個欄位只回答一個問題。若一個欄位同時承載兩個維度，填寫者會混淆，查詢者會誤讀。</p>
<h3 id="判斷標準">判斷標準</h3>
<p>一個欄位的內容若出現以下徵兆，代表「承載太多維度」，應拆分：</p>
<table>
  <thead>
      <tr>
          <th>徵兆</th>
          <th>範例</th>
          <th>拆分方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>描述需要「和」連接</td>
          <td><code>status: &quot;in_progress 且 blocked by API&quot;</code></td>
          <td>拆成 <code>status</code> + <code>blockedBy</code></td>
      </tr>
      <tr>
          <td>同一欄位混用動作與動機</td>
          <td><code>what: &quot;修 bug 因為用戶回報崩潰&quot;</code></td>
          <td>拆成 <code>what</code>（修 bug）+ <code>why</code>（用戶回報崩潰影響可用性）</td>
      </tr>
      <tr>
          <td>單欄位塞多個可查詢值</td>
          <td><code>tags: &quot;p0 security urgent&quot;</code></td>
          <td>改為 <code>priority: p0</code> + <code>category: security</code> + <code>urgency: high</code></td>
      </tr>
      <tr>
          <td>用自由文字藏結構資料</td>
          <td><code>notes: &quot;owner=alice, due=2026-04-20&quot;</code></td>
          <td>拆成 <code>owner: alice</code> + <code>due: 2026-04-20</code></td>
      </tr>
  </tbody>
</table>
<h3 id="反例一個欄位承載兩個維度">反例：一個欄位承載兩個維度</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 錯誤：status 同時表達「進度」和「阻塞原因」</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">status</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;in_progress_waiting_for_api_team&#34;</span></span></span></code></pre></div><p>問題：</p>
<ul>
<li>查詢「所有 in_progress 的 ticket」需要字串前綴比對，不穩定。</li>
<li>改阻塞對象時，狀態字串也要改，污染查詢歷史。</li>
<li>報表無法分別統計「進度分佈」和「阻塞分佈」。</li>
</ul>
<h3 id="正確兩個維度各自一個欄位">正確：兩個維度各自一個欄位</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">status</span><span class="p">:</span><span class="w"> </span><span class="l">in_progress</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">blockedBy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span>- <span class="nt">team</span><span class="p">:</span><span class="w"> </span><span class="l">api</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="nt">reason</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;等待 /v2/users 端點上線&#34;</span></span></span></code></pre></div><p>每個欄位<strong>只回答一個問題</strong>：<code>status</code> 回答「目前在哪個階段」，<code>blockedBy</code> 回答「被什麼擋住」。</p>
<h3 id="原子化測試">原子化測試</h3>
<p>拿一個欄位問三個問題：</p>
<ol>
<li><strong>這個欄位回答什麼問題？</strong> — 若答案需要「和」連接兩個問題，必須拆分。</li>
<li><strong>這個欄位的值能獨立變更嗎？</strong> — 若必須連動其他資訊，代表混在一起了。</li>
<li><strong>能用此欄位單獨做統計/排序嗎？</strong> — 若不能，代表它混入了其他維度。</li>
</ol>
<hr>
<h2 id="2-索引--欄位程式化查詢id-格式frontmatter-設計">2. 索引 × 欄位：程式化查詢、ID 格式、frontmatter 設計</h2>
<p><strong>原則</strong>：frontmatter 欄位存在的理由之一是<strong>讓程式能查詢</strong>。人眼看不出結構，程式才看得出。所以 ID 格式、enum 值、布林欄位的命名要為「程式化查詢」服務。</p>
<h3 id="id-格式設計">ID 格式設計</h3>
<p>好的 ID 同時對人和程式友善。以下是穩定 ID 格式的檢查點：</p>
<table>
  <thead>
      <tr>
          <th>要求</th>
          <th>正確範例</th>
          <th>錯誤範例</th>
          <th>原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>分層結構可拆解</td>
          <td><code>v1.2.0-W03-021.4</code></td>
          <td><code>ticket_74_sub7</code></td>
          <td>前者可用分隔符拆成版本/wave/序號/子序號</td>
      </tr>
      <tr>
          <td>穩定分隔符</td>
          <td>用 <code>-</code> 分層、<code>.</code> 分子層</td>
          <td>混用 <code>_</code>、<code>-</code>、<code> </code></td>
          <td>程式 regex 才能一致比對</td>
      </tr>
      <tr>
          <td>固定位數（可選）</td>
          <td><code>P001</code>、<code>P002</code></td>
          <td><code>P1</code>、<code>P10</code>、<code>P100</code></td>
          <td>字典序排序才會等於數值序</td>
      </tr>
      <tr>
          <td>不含空白與特殊字元</td>
          <td><code>prop-cache-cleanup</code></td>
          <td><code>prop cache cleanup!</code></td>
          <td>避免需要引號與跳脫</td>
      </tr>
      <tr>
          <td>可預測的命名空間</td>
          <td><code>PROP-</code> / <code>UC-</code> / <code>SPEC-</code> 前綴</td>
          <td>無前綴純數字</td>
          <td>能靠前綴快速過濾類別</td>
      </tr>
  </tbody>
</table>
<h3 id="frontmatter-為查詢服務">frontmatter 為查詢服務</h3>
<p>frontmatter 的目的是「讓特定查詢變快」、不是「把資訊塞進去」 — 因為光把資訊塞進去只是換個地方存、查詢仍然要 scan；要查詢變快、欄位要做特定設計（穩定鍵名、可索引的值類型、前綴命名空間）才能匹配查詢工具的索引機制。把目的搞錯會做出「資訊完整但查詢仍要全文掃」的 frontmatter、讀者得到結果但工具拿不到效益。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">ticket-001</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;修復登入崩潰&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="nt">status</span><span class="p">:</span><span class="w"> </span><span class="l">in_progress</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="nt">priority</span><span class="p">:</span><span class="w"> </span><span class="l">P0</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w"></span><span class="nt">blockedBy</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w"></span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="l">v1.2.0</span><span class="w">
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="w"></span><span class="nt">wave</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="w"></span><span class="nn">---</span></span></span></code></pre></div><p>每個欄位服務一種查詢需求：</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>服務什麼查詢</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>status</code></td>
          <td>列出所有「進行中」的項目</td>
      </tr>
      <tr>
          <td><code>priority</code></td>
          <td>依優先順序排序</td>
      </tr>
      <tr>
          <td><code>blockedBy</code></td>
          <td>找出被阻塞的項目</td>
      </tr>
      <tr>
          <td><code>version</code> / <code>wave</code></td>
          <td>按發佈批次篩選</td>
      </tr>
  </tbody>
</table>
<p><strong>欄位不服務的查詢就不要存</strong>。例如「建立者的辦公座位」放在 frontmatter 只會污染欄位密度。</p>
<h3 id="為程式能看優先為人能看是加分">為「程式能看」優先，為「人能看」是加分</h3>
<p>frontmatter 的值優先讓程式 grep/parse，再由顯示層（UI、報表）翻譯給人。</p>
<table>
  <thead>
      <tr>
          <th>優先程式</th>
          <th>優先人</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>status: in_progress</code></td>
          <td><code>status: &quot;進行中（已派發工程師）&quot;</code></td>
      </tr>
      <tr>
          <td><code>priority: P0</code></td>
          <td><code>priority: &quot;緊急 — 阻塞發佈&quot;</code></td>
      </tr>
      <tr>
          <td><code>blockedBy: [ticket-042]</code></td>
          <td><code>blockedBy: &quot;等 ticket-042 的 API 做完&quot;</code></td>
      </tr>
  </tbody>
</table>
<p>若需要給人看的補充描述，用獨立的自由文字欄位承接（如 <code>blockedByReason</code>），frontmatter 主欄位保持結構化。</p>
<hr>
<h2 id="3-意圖顯性--欄位欄位名稱即提問">3. 意圖顯性 × 欄位：欄位名稱即提問</h2>
<p><strong>原則</strong>：欄位名稱本身就是一個問題。填寫者看到欄位名，應立刻知道要填什麼答案。若欄位名需要額外文件解釋，代表命名不夠顯性。</p>
<h3 id="好欄位名--好問題">好欄位名 = 好問題</h3>
<table>
  <thead>
      <tr>
          <th>欄位名</th>
          <th>它問的問題</th>
          <th>填寫者的思考</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>what</code></td>
          <td>「做了什麼？」</td>
          <td>描述動作/內容</td>
      </tr>
      <tr>
          <td><code>why</code></td>
          <td>「為什麼需要？」</td>
          <td>陳述動機/業務理由</td>
      </tr>
      <tr>
          <td><code>when</code></td>
          <td>「什麼時候觸發？」</td>
          <td>條件/時機</td>
      </tr>
      <tr>
          <td><code>where</code></td>
          <td>「影響範圍？」</td>
          <td>檔案/模組/層級</td>
      </tr>
      <tr>
          <td><code>how</code></td>
          <td>「怎麼做？」</td>
          <td>實作策略</td>
      </tr>
      <tr>
          <td><code>acceptance</code></td>
          <td>「怎樣算完成？」</td>
          <td>可驗證條件</td>
      </tr>
      <tr>
          <td><code>blockedBy</code></td>
          <td>「被什麼擋住？」</td>
          <td>依賴項目</td>
      </tr>
      <tr>
          <td><code>owner</code></td>
          <td>「誰負責？」</td>
          <td>單一責任人</td>
      </tr>
      <tr>
          <td><code>deprecatedAt</code></td>
          <td>「何時廢棄？」</td>
          <td>日期或版本號</td>
      </tr>
  </tbody>
</table>
<h3 id="反例名稱無法暗示問題">反例：名稱無法暗示問題</h3>
<table>
  <thead>
      <tr>
          <th>模糊欄位名</th>
          <th>問題</th>
          <th>改善</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>data</code></td>
          <td>什麼資料？</td>
          <td><code>payload</code> / <code>userProfile</code> / <code>metrics</code> 依內容</td>
      </tr>
      <tr>
          <td><code>info</code></td>
          <td>什麼資訊？</td>
          <td><code>description</code> / <code>errorDetail</code> / <code>author</code></td>
      </tr>
      <tr>
          <td><code>meta</code></td>
          <td>描述什麼？</td>
          <td><code>createdBy</code> / <code>source</code> / <code>tags</code></td>
      </tr>
      <tr>
          <td><code>config</code></td>
          <td>配置什麼？</td>
          <td><code>retryPolicy</code> / <code>cacheTtl</code></td>
      </tr>
      <tr>
          <td><code>flag</code></td>
          <td>什麼旗標？</td>
          <td><code>isPublished</code> / <code>hasWarning</code></td>
      </tr>
      <tr>
          <td><code>type</code></td>
          <td>什麼類型？</td>
          <td><code>eventType</code> / <code>userRole</code> / <code>errorCategory</code></td>
      </tr>
  </tbody>
</table>
<h3 id="布林欄位用-is_--has_--can_-開頭">布林欄位用 <code>is_</code> / <code>has_</code> / <code>can_</code> 開頭</h3>
<table>
  <thead>
      <tr>
          <th>錯誤</th>
          <th>正確</th>
          <th>原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>active</code></td>
          <td><code>isActive</code></td>
          <td>看到就知道是 true/false</td>
      </tr>
      <tr>
          <td><code>permission</code></td>
          <td><code>hasPermission</code></td>
          <td>暗示「擁有關係」</td>
      </tr>
      <tr>
          <td><code>edit</code></td>
          <td><code>canEdit</code></td>
          <td>暗示「能力檢查」</td>
      </tr>
      <tr>
          <td><code>visible</code></td>
          <td><code>isVisible</code></td>
          <td>避免與名詞混淆</td>
      </tr>
  </tbody>
</table>
<h3 id="欄位名稱體現抽象層">欄位名稱體現抽象層</h3>
<p>一份文件內的欄位應處於<strong>同一抽象層</strong>。混層會讓讀者不知道該看哪個。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 錯誤：混抽象層</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">what</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;修 bug&#34;</span><span class="w">                           </span><span class="c"># 業務層</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="nt">implementationDetail</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;修改 auth.py 第 42 行&#34;</span><span class="w">  </span><span class="c"># 實作層</span></span></span></code></pre></div><p>改善：<code>what</code> 留在業務層，實作細節進 <code>how</code> 或 <code>where.files</code>。</p>
<hr>
<h2 id="4-可查詢性--欄位值格式一致性enum-命名">4. 可查詢性 × 欄位：值格式一致性、enum 命名</h2>
<p><strong>原則</strong>：同一個欄位的值要有<strong>穩定格式</strong>，讓 grep/filter/sort 可預測。格式不一致會讓查詢需要 N 個 regex 才能覆蓋，最終退化成「用肉眼翻」。</p>
<h3 id="enum-優於自由文字">enum 優於自由文字</h3>
<p>有限集合的欄位應列出所有合法值：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 正確：status 有固定集合</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">status</span><span class="p">:</span><span class="w"> </span><span class="l">in_progress   </span><span class="w"> </span><span class="c"># 僅限 pending / in_progress / blocked / completed / cancelled</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="c"># 錯誤：狀態用自由文字</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="nt">status</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;進行中，但有點卡&#34;</span></span></span></code></pre></div><p>enum 的三個好處：</p>
<ol>
<li>grep 精確（<code>status: in_progress</code> 無歧義）</li>
<li>值變更能被編譯器/驗證器捕捉</li>
<li>統計時不用做語意聚類</li>
</ol>
<h3 id="enum-命名規則">enum 命名規則</h3>
<table>
  <thead>
      <tr>
          <th>要求</th>
          <th>正確</th>
          <th>錯誤</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>全小寫、單字用 <code>_</code> 連接</td>
          <td><code>in_progress</code></td>
          <td><code>inProgress</code> / <code>In-Progress</code></td>
      </tr>
      <tr>
          <td>語意中立（不帶情緒）</td>
          <td><code>cancelled</code></td>
          <td><code>abandoned_by_team</code></td>
      </tr>
      <tr>
          <td>涵蓋周延（加 <code>unknown</code> 或 <code>other</code> 兜底）</td>
          <td><code>unknown</code></td>
          <td>漏 fallback 導致必填卡關</td>
      </tr>
      <tr>
          <td>避免數字後綴（除非真的有序）</td>
          <td><code>high</code> / <code>medium</code> / <code>low</code></td>
          <td><code>level1</code> / <code>level2</code></td>
      </tr>
  </tbody>
</table>
<h3 id="複合值的穩定分隔符">複合值的穩定分隔符</h3>
<p>若欄位必須承載複合值，用<strong>穩定的分隔符</strong>讓 regex 可拆。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 正確：用 : 分隔方向和目標</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">direction</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;to-sibling:ticket-045&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="c"># 錯誤：自由文字</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="nt">direction</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;指向兄弟 ticket 045&#34;</span></span></span></code></pre></div><p>常見分隔符慣例：</p>
<table>
  <thead>
      <tr>
          <th>分隔符</th>
          <th>用途</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>:</code></td>
          <td>維度:值</td>
          <td><code>scope:api</code>、<code>type:bug</code></td>
      </tr>
      <tr>
          <td><code>/</code></td>
          <td>路徑</td>
          <td><code>src/auth/login.py</code></td>
      </tr>
      <tr>
          <td><code>,</code></td>
          <td>多值列表（若不用陣列）</td>
          <td><code>a11y,i18n,perf</code></td>
      </tr>
      <tr>
          <td><code>→</code> / <code>-&gt;</code></td>
          <td>流向</td>
          <td><code>pending → in_progress</code></td>
      </tr>
  </tbody>
</table>
<p><strong>禁止混用分隔符</strong>。若一個欄位用 <code>:</code>，整個系統都要用 <code>:</code>。</p>
<h3 id="欄位值前綴做分類">欄位值前綴做分類</h3>
<p>當同類型但需細分時，用固定前綴而非混在自由文字中。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 正確：ID 前綴暗示類別</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">id: PROP-042    # proposal (portability-allow</span><span class="p">:</span><span class="w"> </span><span class="l">teaching example)</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">UC-007     </span><span class="w"> </span><span class="c"># use case</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">SPEC-012   </span><span class="w"> </span><span class="c"># spec</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w"></span><span class="c"># 錯誤：自由文字描述類別</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w"></span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;proposal 42&#34;</span></span></span></code></pre></div><h3 id="日期時間統一-iso-8601">日期時間統一 ISO 8601</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 正確</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">createdAt</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;2026-04-16&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="nt">startedAt</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;2026-04-16T09:30:00+08:00&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="c"># 錯誤</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w"></span><span class="nt">createdAt</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;2026/4/16&#34;</span><span class="w">       </span><span class="c"># 分隔符不穩</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w"></span><span class="nt">createdAt</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;April 16, 2026&#34;</span><span class="w">  </span><span class="c"># 不可排序</span><span class="w">
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="w"></span><span class="nt">createdAt</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;昨天&#34;</span><span class="w">             </span><span class="c"># 不可重複解析</span></span></span></code></pre></div><hr>
<h2 id="5-欄位設計-meta新增欄位的決策框架">5. 欄位設計 meta：新增欄位的決策框架</h2>
<p><strong>原則</strong>：欄位一旦加入就難以撤回。新增前必問以下七個問題，避免欄位膨脹、語意重疊。</p>
<h3 id="新增欄位前必問清單">新增欄位前必問清單</h3>
<table>
  <thead>
      <tr>
          <th>#</th>
          <th>問題</th>
          <th>若答「否」的處理</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td><strong>這個欄位回答什麼問題？（單一維度）</strong></td>
          <td>若回答多個問題 → 拆成多欄位</td>
      </tr>
      <tr>
          <td>2</td>
          <td><strong>是否已有欄位涵蓋同一維度？</strong></td>
          <td>若有 → 擴充既有欄位或重新命名，不新增</td>
      </tr>
      <tr>
          <td>3</td>
          <td><strong>至少有一個查詢情境需要它嗎？</strong></td>
          <td>若無 → 放入自由文字 notes，不進 frontmatter</td>
      </tr>
      <tr>
          <td>4</td>
          <td><strong>值域是否有限可枚舉？</strong></td>
          <td>若是 → 用 enum；若否 → 確認自由文字真的必要</td>
      </tr>
      <tr>
          <td>5</td>
          <td><strong>缺省值（missing）有明確語意嗎？</strong></td>
          <td>若無 → 補上預設值或註明 <code>nullable</code></td>
      </tr>
      <tr>
          <td>6</td>
          <td><strong>填寫者有能力正確填寫嗎？</strong></td>
          <td>若否 → 改成程式自動填，或提供 enum 選單</td>
      </tr>
      <tr>
          <td>7</td>
          <td><strong>停用時如何淘汰？</strong></td>
          <td>若無計畫 → 先標註 <code>deprecatedAt</code> 欄位策略</td>
      </tr>
  </tbody>
</table>
<h3 id="欄位語意重疊檢查">欄位語意重疊檢查</h3>
<p>新增欄位時最常見的錯誤是「與既有欄位語意重疊」。檢查方式：</p>
<ol>
<li>列出新欄位的<strong>提問</strong>（它要回答什麼問題）。</li>
<li>對照既有欄位的<strong>提問</strong>，找是否有重複。</li>
<li>若有重複，選其一：
<ul>
<li><strong>合併</strong>：新增的維度其實可以塞進既有欄位（若不違反原子化）。</li>
<li><strong>改名</strong>：讓兩個欄位的提問有明確區別。</li>
<li><strong>捨棄</strong>：若既有欄位已能滿足，不新增。</li>
</ul>
</li>
</ol>
<h3 id="欄位膨脹的警訊">欄位膨脹的警訊</h3>
<table>
  <thead>
      <tr>
          <th>警訊</th>
          <th>意義</th>
          <th>行動</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>一個 schema 超過 15 個 frontmatter 欄位</td>
          <td>單一物件承載過多責任</td>
          <td>分拆為子物件（nested）或獨立 schema</td>
      </tr>
      <tr>
          <td>有些欄位在 &gt; 80% 的資料中為空</td>
          <td>該欄位的查詢需求薄弱</td>
          <td>降級為自由文字 notes</td>
      </tr>
      <tr>
          <td>欄位的填寫規則寫在多份 wiki</td>
          <td>規則太複雜</td>
          <td>改為 enum 或引入驗證器</td>
      </tr>
      <tr>
          <td>兩個欄位經常「一起填/一起空」</td>
          <td>語意耦合</td>
          <td>合併或用 nested 結構</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="6-ticket-六欄位角度解析">6. Ticket 六欄位角度解析</h2>
<p>ticket 模板的 <code>what / why / when / where / how / acceptance</code> 六欄位是<strong>刻意設計的多角度描述系統</strong>。每個欄位從不同角度描述同一個 ticket，合起來才是完整規格。</p>
<h3 id="六欄位角度總表">六欄位角度總表</h3>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>角度</th>
          <th>正確範例摘要</th>
          <th>不該寫什麼</th>
          <th>常見混淆模式</th>
          <th>檢驗標準</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>what</code></td>
          <td>做什麼</td>
          <td><code>&quot;新增 2FA 設定開關，支援 TOTP 與 email 驗證碼&quot;</code></td>
          <td>動機、實作、驗收</td>
          <td>用「因為&hellip;所以」開頭，把 <code>why</code> 塞進來</td>
          <td>讀者能立刻想像成品</td>
      </tr>
      <tr>
          <td><code>why</code></td>
          <td>為什麼做</td>
          <td><code>&quot;登入崩潰影響 8% 用戶，每週 40 張客服工單&quot;</code></td>
          <td>動作、實作細節</td>
          <td>寫「要修 X bug，修改 Y 檔案」，把 <code>what</code>/<code>how</code> 塞進來</td>
          <td>含業務指標或成本數字</td>
      </tr>
      <tr>
          <td><code>when</code></td>
          <td>何時啟動 / 何時截止</td>
          <td><code>&quot;v1.2.0 發佈前；依賴 W03-021 完成後啟動&quot;</code></td>
          <td>產品行為時序</td>
          <td>寫「當用戶點擊&hellip;系統會&hellip;」，描述產品行為而非 ticket 啟動條件</td>
          <td>條件可被程式或人工驗證</td>
      </tr>
      <tr>
          <td><code>where</code></td>
          <td>影響範圍</td>
          <td><code>layer: Application; files: [src/auth/login_service.py, ...]</code></td>
          <td>抽象功能名</td>
          <td>寫「相關的所有地方」，不列具體路徑</td>
          <td>有具體檔案/模組清單</td>
      </tr>
      <tr>
          <td><code>how</code></td>
          <td>怎麼做</td>
          <td><code>&quot;加 guard clause → Result 模式 → 補 6 個失敗情境測試&quot;</code></td>
          <td>驗收條件、動機</td>
          <td>寫「做完要通過所有測試，成功率 ≥ 99.5%」，把 <code>acceptance</code> 塞進來</td>
          <td>有步驟、順序、技術選擇</td>
      </tr>
      <tr>
          <td><code>acceptance</code></td>
          <td>怎樣算完成</td>
          <td><code>&quot;[ ] Staging 連續 24h 成功率 ≥ 99.5%&quot;</code></td>
          <td>做法、動機</td>
          <td>寫「體驗變好」「品質提升」等不可量測描述</td>
          <td>每條都能被勾選（量化或證據性）</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p><strong>詳細範例</strong>（每個欄位各 1 個正確 + 1 個混淆共 12 項）：<code>designing-fields-ticket-6w.md</code></p></blockquote>
<hr>
<h2 id="7-非-ticket-情境yaml-配置api-response">7. 非 ticket 情境：YAML 配置、API response</h2>
<p>核心原則不只適用 ticket。以下兩個情境示範同樣的思維。</p>
<h3 id="71-yaml-配置檔案">7.1 YAML 配置檔案</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>用 namespace 分組（<code>database.pool.size</code> 而非 <code>dbPoolSize</code>）</td>
      </tr>
      <tr>
          <td>意圖顯性</td>
          <td><code>retryMaxAttempts</code> 優於 <code>retry</code> 或 <code>attempts</code></td>
      </tr>
      <tr>
          <td>可查詢性</td>
          <td>布林用 <code>isEnabled</code> / <code>hasFallback</code>；時長用 <code>timeoutSeconds</code> 後綴</td>
      </tr>
      <tr>
          <td>欄位設計</td>
          <td>新增配置前問「這個值會隨環境變嗎？（是→配置；否→常數）」</td>
      </tr>
  </tbody>
</table>
<p><strong>範例（正確）</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">  </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;db.internal&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">5432</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="nt">pool</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nt">minSize</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">maxSize</span><span class="p">:</span><span class="w"> </span><span class="m">50</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nt">acquireTimeoutSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w"></span><span class="nt">retry</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">  </span><span class="nt">maxAttempts</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">  </span><span class="nt">initialDelayMs</span><span class="p">:</span><span class="w"> </span><span class="m">100</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">  </span><span class="nt">backoffMultiplier</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">  </span><span class="nt">retryableErrors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span>- <span class="l">connection_timeout</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">    </span>- <span class="l">transient_network_error</span></span></span></code></pre></div><p><strong>範例（常見混淆）</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 錯誤：單位混亂、命名模糊、enum 變自由文字</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">db</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">          </span><span class="c"># 秒？毫秒？</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span><span class="nt">retry</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">          </span><span class="c"># 布林還是次數？</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">  </span><span class="nt">errors</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;timeout,network&#34;</span><span class="w">  </span><span class="c"># 為何不用列表？</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">  </span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;production, maybe&#34;</span><span class="w">  </span><span class="c"># enum 還是形容詞？</span></span></span></code></pre></div><p><strong>混淆分析</strong>：</p>
<ul>
<li><code>timeout</code> 沒標單位 → 應為 <code>timeoutSeconds</code> 或 <code>timeoutMs</code></li>
<li><code>retry: true</code> 語意不清 → 改為 <code>retry.isEnabled</code> + <code>retry.maxAttempts</code></li>
<li><code>errors</code> 用字串列表 → 應為 YAML 陣列以利 parse</li>
<li><code>mode</code> 含「maybe」 → enum 必須是有限集合</li>
</ul>
<hr>
<h3 id="72-api-response-schema">7.2 API Response Schema</h3>
<p><strong>原則應用</strong>：</p>
<table>
  <thead>
      <tr>
          <th>原則</th>
          <th>在 API response 的具體形式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>原子化</td>
          <td>資料欄位與 meta 欄位分開（資料放 <code>data</code>，狀態放 <code>status</code>）</td>
      </tr>
      <tr>
          <td>索引</td>
          <td>穩定 ID 欄位（<code>id</code>、<code>cursor</code>）讓分頁與查詢可預測</td>
      </tr>
      <tr>
          <td>意圖顯性</td>
          <td><code>isPaginated</code>、<code>hasMore</code>、<code>totalCount</code> 各司其職</td>
      </tr>
      <tr>
          <td>可查詢性</td>
          <td>錯誤碼用 enum（<code>errorCode: &quot;INVALID_TOKEN&quot;</code>）而非自由文字</td>
      </tr>
      <tr>
          <td>欄位設計</td>
          <td>新增欄位前問「client 真的會用嗎？還是只是 server 方便？」</td>
      </tr>
  </tbody>
</table>
<p><strong>範例（正確）</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="nt">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;success&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nt">&#34;users&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">      <span class="p">{</span> <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;usr_001&#34;</span><span class="p">,</span> <span class="nt">&#34;email&#34;</span><span class="p">:</span> <span class="s2">&#34;alice@example.com&#34;</span><span class="p">,</span> <span class="nt">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;admin&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <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="nt">&#34;pagination&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nt">&#34;hasMore&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nt">&#34;nextCursor&#34;</span><span class="p">:</span> <span class="s2">&#34;eyJpZCI6InVzcl8wMDEifQ==&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nt">&#34;totalCount&#34;</span><span class="p">:</span> <span class="mi">1247</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="nt">&#34;meta&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="nt">&#34;requestId&#34;</span><span class="p">:</span> <span class="s2">&#34;req_abc123&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nt">&#34;serverTime&#34;</span><span class="p">:</span> <span class="s2">&#34;2026-04-16T09:30:00Z&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>範例（常見混淆）</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;ok&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nt">&#34;result&#34;</span><span class="p">:</span> <span class="s2">&#34;...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nt">&#34;error&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nt">&#34;nextPage&#34;</span><span class="p">:</span> <span class="s2">&#34;yes&#34;</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="nt">&#34;info&#34;</span><span class="p">:</span> <span class="s2">&#34;取得 10 筆資料，還有更多&#34;</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>混淆分析</strong>：</p>
<ul>
<li><code>ok: 1</code> 數字當布林 → 應為 <code>status: &quot;success&quot;</code> enum</li>
<li><code>data</code> 同時放業務資料與錯誤（<code>result</code> + <code>error</code>）→ 原子化違反，應分開</li>
<li><code>nextPage: &quot;yes&quot;</code> 字串當布林 → 應為 <code>hasMore: true</code></li>
<li><code>info</code> 為自由文字夾帶結構資訊 → 拆成 <code>totalCount: 10</code> + <code>hasMore: true</code></li>
</ul>
<hr>
<h2 id="可攜性自檢">可攜性自檢</h2>
<p>本文件可獨立閱讀，不引用任何特定專案的路徑、ticket ID、commit hash 或 hook 系統。核心原則與 ticket 六欄位範例皆為通用概念，可套用於任何採用多欄位結構的文件系統。</p>
<hr>
<h2 id="速查表">速查表</h2>
<table>
  <thead>
      <tr>
          <th>想做的事</th>
          <th>看哪一節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>判斷欄位是否承載太多維度</td>
          <td>第 1 節「原子化測試」</td>
      </tr>
      <tr>
          <td>設計 ID 格式</td>
          <td>第 2 節「ID 格式設計」</td>
      </tr>
      <tr>
          <td>讓欄位名自我說明</td>
          <td>第 3 節「好欄位名 = 好問題」</td>
      </tr>
      <tr>
          <td>enum 命名規則</td>
          <td>第 4 節「enum 命名規則」</td>
      </tr>
      <tr>
          <td>新增欄位前的檢核</td>
          <td>第 5 節「新增欄位前必問清單」</td>
      </tr>
      <tr>
          <td>ticket what vs why 混淆</td>
          <td>第 6 節「六欄位角度總表」+ <code>designing-fields-ticket-6w.md</code> <code>what</code>/<code>why</code> 章節</td>
      </tr>
      <tr>
          <td>可驗證的 acceptance 寫法</td>
          <td>第 6 節「六欄位角度總表」+ <code>designing-fields-ticket-6w.md</code> <code>acceptance</code> 章節</td>
      </tr>
      <tr>
          <td>YAML 配置命名</td>
          <td>第 7.1</td>
      </tr>
      <tr>
          <td>API response 欄位設計</td>
          <td>第 7.2</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>Designing Fields Ticket 6W — 六欄位詳細範例</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/designing-fields-ticket-6w/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/designing-fields-ticket-6w/</guid><description>&lt;p>本文件為 &lt;code>designing-fields.md&lt;/code> Ticket 六欄位角度解析段的詳細附錄。
每個欄位提供 1 個正確範例 + 1 個常見混淆範例，共 12 項。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>前置閱讀&lt;/strong>：先讀 &lt;code>designing-fields.md&lt;/code>「六欄位角度總表」，理解六個欄位的角色分工後再閱讀本文件。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="what-欄位">&lt;code>what&lt;/code> 欄位&lt;/h2>
&lt;h3 id="正確範例">正確範例&lt;/h3>
&lt;p>&lt;strong>欄位提問&lt;/strong>：這個 ticket 要做什麼？（描述動作/內容，不含動機）&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nt">what&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;在使用者設定頁新增「雙因素驗證（2FA）」開關，支援 TOTP 與 email 驗證碼兩種方式&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>為什麼正確&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>描述「做了什麼」（新增開關、支援兩種方式）&lt;/li>
&lt;li>不解釋為什麼做（動機屬於 &lt;code>why&lt;/code>）&lt;/li>
&lt;li>不描述怎麼做（實作策略屬於 &lt;code>how&lt;/code>）&lt;/li>
&lt;li>範圍具體（使用者設定頁、TOTP + email），閱讀者能立刻想像成品&lt;/li>
&lt;/ul>
&lt;h3 id="常見混淆把動機寫進-what">常見混淆：把動機寫進 what&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c"># 錯誤&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">what&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;因為最近有用戶帳號被盜，所以要加強安全，做一個 2FA&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>為什麼混淆&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>「因為&amp;hellip;所以」的結構顯示這是動機（why），不是動作（what）&lt;/li>
&lt;li>讀者需要剝離「因為&amp;hellip;」才能看到真正的動作&lt;/li>
&lt;li>與 &lt;code>why&lt;/code> 欄位內容重複，降低欄位密度&lt;/li>
&lt;li>查詢「做了什麼」時會讀到動機雜訊&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>改善&lt;/strong>：動機搬到 &lt;code>why&lt;/code>，&lt;code>what&lt;/code> 只留動作：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nt">what&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;新增 2FA 設定開關，支援 TOTP 與 email 驗證碼&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">why&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;最近三個月有 N 起帳號盜用事件，2FA 可降低風險&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="why-欄位">&lt;code>why&lt;/code> 欄位&lt;/h2>
&lt;h3 id="正確範例-1">正確範例&lt;/h3>
&lt;p>&lt;strong>欄位提問&lt;/strong>：為什麼需要做這件事？（業務動機，不含實作原因）&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nt">why&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;過去三個月登入頁崩潰影響 8% 活躍用戶，平均每位受影響用戶嘗試 3 次才成功登入。修復後預期可提升首週留存 1.5 個百分點，並減少客服工單量（當前每週 40 張與登入相關）&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>為什麼正確&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>陳述業務動機（用戶影響、留存、客服成本）&lt;/li>
&lt;li>含具體數字讓重要性可衡量&lt;/li>
&lt;li>不解釋「怎麼修」（那是 how 的責任）&lt;/li>
&lt;li>不描述「做什麼」（那是 what 的責任）&lt;/li>
&lt;/ul>
&lt;h3 id="常見混淆把-what-寫進-why">常見混淆：把 what 寫進 why&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c"># 錯誤&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">why&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;要修復登入頁的崩潰 bug，會修改 auth.py 第 42 行並加 try-catch&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>為什麼混淆&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>「修復崩潰 bug」是 what，不是 why&lt;/li>
&lt;li>「修改 auth.py 第 42 行」是 how（實作細節）&lt;/li>
&lt;li>完全沒回答「為什麼要修」的業務問題&lt;/li>
&lt;li>讀者讀完 &lt;code>why&lt;/code> 仍不知道這個 ticket 的價值在哪&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>改善&lt;/strong>：動機與實作各歸各位：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nt">why&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;登入崩潰導致 8% 用戶流失，每週產生 40 張客服工單&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">what&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;修復登入頁 authentication flow 的 null pointer&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">how&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;在 auth.py token 解析前加 guard clause；補 unit test 覆蓋 null token 情境&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="when-欄位">&lt;code>when&lt;/code> 欄位&lt;/h2>
&lt;h3 id="正確範例-2">正確範例&lt;/h3>
&lt;p>&lt;strong>欄位提問&lt;/strong>：什麼時候觸發/執行這個 ticket？（條件/時機，不是動作內容）&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nt">when&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;v1.2.0 發佈前（預計 2026-05-10），依賴 W03-021 架構草案完成後即可啟動&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># portability-allow: educational when-field example&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>為什麼正確&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>回答「什麼時候」的問題（版本截止、前置依賴）&lt;/li>
&lt;li>條件可被驗證（檢查 W03-021 狀態即可）&lt;!-- portability-allow: educational reference example -->&lt;/li>
&lt;li>不混入「要做什麼」或「為什麼」&lt;/li>
&lt;/ul>
&lt;h3 id="常見混淆把-what-重述一遍">常見混淆：把 what 重述一遍&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c"># 錯誤&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">when&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;當使用者點擊登入按鈕時，系統會驗證帳密並嘗試登入&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>為什麼混淆&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>這描述的是&lt;strong>產品行為的觸發時機&lt;/strong>，不是 ticket 執行的時機&lt;/li>
&lt;li>把 &lt;code>what&lt;/code>（驗證流程）用時序包裝後重述&lt;/li>
&lt;li>真正的 &lt;code>when&lt;/code>（ticket 啟動條件）完全缺失&lt;/li>
&lt;li>讀者不知道何時該開始做這個 ticket&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>改善&lt;/strong>：分清「ticket 啟動時機」與「產品行為時機」：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nt">when&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;登入崩潰回報量超過每週 20 起即啟動；最遲在下一個 minor 版本發佈前完成&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">what&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;修復登入流程：驗證帳密 → 產生 session → 導向首頁&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="where-欄位">&lt;code>where&lt;/code> 欄位&lt;/h2>
&lt;h3 id="正確範例-3">正確範例&lt;/h3>
&lt;p>&lt;strong>欄位提問&lt;/strong>：影響哪些檔案/模組/層級？（範圍定位，不是做什麼）&lt;/p></description><content:encoded><![CDATA[<p>本文件為 <code>designing-fields.md</code> Ticket 六欄位角度解析段的詳細附錄。
每個欄位提供 1 個正確範例 + 1 個常見混淆範例，共 12 項。</p>
<blockquote>
<p><strong>前置閱讀</strong>：先讀 <code>designing-fields.md</code>「六欄位角度總表」，理解六個欄位的角色分工後再閱讀本文件。</p></blockquote>
<hr>
<h2 id="what-欄位"><code>what</code> 欄位</h2>
<h3 id="正確範例">正確範例</h3>
<p><strong>欄位提問</strong>：這個 ticket 要做什麼？（描述動作/內容，不含動機）</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">what</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;在使用者設定頁新增「雙因素驗證（2FA）」開關，支援 TOTP 與 email 驗證碼兩種方式&#34;</span></span></span></code></pre></div><p><strong>為什麼正確</strong>：</p>
<ul>
<li>描述「做了什麼」（新增開關、支援兩種方式）</li>
<li>不解釋為什麼做（動機屬於 <code>why</code>）</li>
<li>不描述怎麼做（實作策略屬於 <code>how</code>）</li>
<li>範圍具體（使用者設定頁、TOTP + email），閱讀者能立刻想像成品</li>
</ul>
<h3 id="常見混淆把動機寫進-what">常見混淆：把動機寫進 what</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 錯誤</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">what</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;因為最近有用戶帳號被盜，所以要加強安全，做一個 2FA&#34;</span></span></span></code></pre></div><p><strong>為什麼混淆</strong>：</p>
<ul>
<li>「因為&hellip;所以」的結構顯示這是動機（why），不是動作（what）</li>
<li>讀者需要剝離「因為&hellip;」才能看到真正的動作</li>
<li>與 <code>why</code> 欄位內容重複，降低欄位密度</li>
<li>查詢「做了什麼」時會讀到動機雜訊</li>
</ul>
<p><strong>改善</strong>：動機搬到 <code>why</code>，<code>what</code> 只留動作：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">what</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;新增 2FA 設定開關，支援 TOTP 與 email 驗證碼&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">why</span><span class="p">:</span><span class="w">  </span><span class="s2">&#34;最近三個月有 N 起帳號盜用事件，2FA 可降低風險&#34;</span></span></span></code></pre></div><hr>
<h2 id="why-欄位"><code>why</code> 欄位</h2>
<h3 id="正確範例-1">正確範例</h3>
<p><strong>欄位提問</strong>：為什麼需要做這件事？（業務動機，不含實作原因）</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">why</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;過去三個月登入頁崩潰影響 8% 活躍用戶，平均每位受影響用戶嘗試 3 次才成功登入。修復後預期可提升首週留存 1.5 個百分點，並減少客服工單量（當前每週 40 張與登入相關）&#34;</span></span></span></code></pre></div><p><strong>為什麼正確</strong>：</p>
<ul>
<li>陳述業務動機（用戶影響、留存、客服成本）</li>
<li>含具體數字讓重要性可衡量</li>
<li>不解釋「怎麼修」（那是 how 的責任）</li>
<li>不描述「做什麼」（那是 what 的責任）</li>
</ul>
<h3 id="常見混淆把-what-寫進-why">常見混淆：把 what 寫進 why</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 錯誤</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">why</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;要修復登入頁的崩潰 bug，會修改 auth.py 第 42 行並加 try-catch&#34;</span></span></span></code></pre></div><p><strong>為什麼混淆</strong>：</p>
<ul>
<li>「修復崩潰 bug」是 what，不是 why</li>
<li>「修改 auth.py 第 42 行」是 how（實作細節）</li>
<li>完全沒回答「為什麼要修」的業務問題</li>
<li>讀者讀完 <code>why</code> 仍不知道這個 ticket 的價值在哪</li>
</ul>
<p><strong>改善</strong>：動機與實作各歸各位：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">why</span><span class="p">:</span><span class="w">  </span><span class="s2">&#34;登入崩潰導致 8% 用戶流失，每週產生 40 張客服工單&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">what</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;修復登入頁 authentication flow 的 null pointer&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="nt">how</span><span class="p">:</span><span class="w">  </span><span class="s2">&#34;在 auth.py token 解析前加 guard clause；補 unit test 覆蓋 null token 情境&#34;</span></span></span></code></pre></div><hr>
<h2 id="when-欄位"><code>when</code> 欄位</h2>
<h3 id="正確範例-2">正確範例</h3>
<p><strong>欄位提問</strong>：什麼時候觸發/執行這個 ticket？（條件/時機，不是動作內容）</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;v1.2.0 發佈前（預計 2026-05-10），依賴 W03-021 架構草案完成後即可啟動&#34;</span><span class="w"> </span><span class="c"># portability-allow: educational when-field example</span></span></span></code></pre></div><p><strong>為什麼正確</strong>：</p>
<ul>
<li>回答「什麼時候」的問題（版本截止、前置依賴）</li>
<li>條件可被驗證（檢查 W03-021 狀態即可）<!-- portability-allow: educational reference example --></li>
<li>不混入「要做什麼」或「為什麼」</li>
</ul>
<h3 id="常見混淆把-what-重述一遍">常見混淆：把 what 重述一遍</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 錯誤</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;當使用者點擊登入按鈕時，系統會驗證帳密並嘗試登入&#34;</span></span></span></code></pre></div><p><strong>為什麼混淆</strong>：</p>
<ul>
<li>這描述的是<strong>產品行為的觸發時機</strong>，不是 ticket 執行的時機</li>
<li>把 <code>what</code>（驗證流程）用時序包裝後重述</li>
<li>真正的 <code>when</code>（ticket 啟動條件）完全缺失</li>
<li>讀者不知道何時該開始做這個 ticket</li>
</ul>
<p><strong>改善</strong>：分清「ticket 啟動時機」與「產品行為時機」：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;登入崩潰回報量超過每週 20 起即啟動；最遲在下一個 minor 版本發佈前完成&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">what</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;修復登入流程：驗證帳密 → 產生 session → 導向首頁&#34;</span></span></span></code></pre></div><hr>
<h2 id="where-欄位"><code>where</code> 欄位</h2>
<h3 id="正確範例-3">正確範例</h3>
<p><strong>欄位提問</strong>：影響哪些檔案/模組/層級？（範圍定位，不是做什麼）</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">where</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span><span class="nt">layer</span><span class="p">:</span><span class="w"> </span><span class="l">Application</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span><span class="nt">files</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span>- <span class="l">src/auth/login_service.py</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span>- <span class="l">src/auth/session_manager.py</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span>- <span class="l">tests/auth/test_login_flow.py</span></span></span></code></pre></div><p><strong>為什麼正確</strong>：</p>
<ul>
<li>明確列出修改範圍（檔案 + 層級）</li>
<li>讓驗收者知道要檢查哪些檔案</li>
<li>協作者能預判 merge 衝突</li>
<li>不混入動作描述</li>
</ul>
<h3 id="常見混淆寫抽象功能而非具體位置">常見混淆：寫抽象功能而非具體位置</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 錯誤</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">where</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;使用者登入流程相關的所有地方&#34;</span></span></span></code></pre></div><p><strong>為什麼混淆</strong>：</p>
<ul>
<li>「所有地方」無法定位，等於沒說</li>
<li>驗收者無法確認範圍是否完整</li>
<li>協作者無法預判衝突</li>
<li>若未來 refactor，沒有具體檔案可追溯</li>
</ul>
<p><strong>改善</strong>：改為具體檔案清單與層級：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">where</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span><span class="nt">layer</span><span class="p">:</span><span class="w"> </span><span class="l">Application + Infrastructure</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span><span class="nt">files</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span>- <span class="l">src/auth/login_service.py       </span><span class="w"> </span><span class="c"># 主邏輯</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span>- <span class="l">src/auth/token_validator.py     </span><span class="w"> </span><span class="c"># token 驗證</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span>- <span class="l">src/infra/redis_session_store.py</span><span class="w"> </span><span class="c"># session 儲存</span></span></span></code></pre></div><hr>
<h2 id="how-欄位"><code>how</code> 欄位</h2>
<h3 id="正確範例-4">正確範例</h3>
<p><strong>欄位提問</strong>：用什麼策略/順序實作？（實作計畫，不是業務內容）</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">how</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span><span class="nt">task_type</span><span class="p">:</span><span class="w"> </span><span class="l">Implementation</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span><span class="nt">strategy</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="sd">    1. 在 token_validator 加 guard clause 處理 null/malformed token
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="sd">    2. 將 session_manager 的錯誤處理改為 Result 模式而非例外
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="sd">    3. 補 unit test 覆蓋 6 種失敗情境（null / expired / malformed / revoked / ip_mismatch / ua_mismatch）
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="sd">    4. 跑 integration test 驗證與 Redis 互動正確</span></span></span></code></pre></div><p><strong>為什麼正確</strong>：</p>
<ul>
<li>提供實作步驟與順序</li>
<li>選擇具體技術方案（guard clause、Result 模式）</li>
<li>不重述業務需求（那是 what）</li>
<li>不寫動機（那是 why）</li>
</ul>
<h3 id="常見混淆把-acceptance-寫進-how">常見混淆：把 acceptance 寫進 how</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 錯誤</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">how</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span><span class="nt">strategy</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;做完要通過所有單元測試，且 login 成功率回到 99.5% 以上&#34;</span></span></span></code></pre></div><p><strong>為什麼混淆</strong>：</p>
<ul>
<li>「通過測試」「成功率 99.5%」是驗收條件（acceptance），不是實作策略</li>
<li>沒回答「怎麼做」的問題</li>
<li>讀者不知道實際要改什麼</li>
<li>驗收與實作責任混淆</li>
</ul>
<p><strong>改善</strong>：分離「做法」與「驗收標準」：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">how</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span><span class="nt">strategy</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;先加 guard clause → 改為 Result 模式 → 補單元測試 → 跑整合測試&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="nt">acceptance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span>- <span class="s2">&#34;[ ] 所有單元測試通過&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">  </span>- <span class="s2">&#34;[ ] Staging 環境 login 成功率 ≥ 99.5%（觀察 24 小時）&#34;</span></span></span></code></pre></div><hr>
<h2 id="acceptance-欄位"><code>acceptance</code> 欄位</h2>
<h3 id="正確範例-5">正確範例</h3>
<p><strong>欄位提問</strong>：怎樣算完成？（可驗證的條件清單）</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">acceptance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span>- <span class="s2">&#34;[ ] auth 模組所有單元測試通過（含新增的 6 個失敗情境測試）&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span>- <span class="s2">&#34;[ ] Staging 環境連續 24 小時 login 成功率 ≥ 99.5%&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span>- <span class="s2">&#34;[ ] 客服工單中「登入無法使用」類別在部署後 7 天內下降 ≥ 50%&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">  </span>- <span class="s2">&#34;[ ] Code review 由另一位後端工程師核可（需涵蓋錯誤處理章節）&#34;</span></span></span></code></pre></div><p><strong>為什麼正確</strong>：</p>
<ul>
<li>每條都<strong>可驗證</strong>（有明確判斷標準）</li>
<li>每條都<strong>可觀察</strong>（測試結果、指標、核可紀錄）</li>
<li>數字具體（99.5%、24 小時、50%、7 天）</li>
<li>不抽象（沒有「讓系統更穩定」這種不可驗證的描述）</li>
</ul>
<h3 id="常見混淆不可驗證的模糊條件">常見混淆：不可驗證的模糊條件</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 錯誤</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">acceptance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span>- <span class="s2">&#34;[ ] 使用者體驗變好&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span>- <span class="s2">&#34;[ ] 程式碼品質提升&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">  </span>- <span class="s2">&#34;[ ] 沒有引入新的 bug&#34;</span></span></span></code></pre></div><p><strong>為什麼混淆</strong>：</p>
<ul>
<li>「體驗變好」無法量測（多好算好？）</li>
<li>「品質提升」主觀（誰評？）</li>
<li>「沒有新 bug」無法證明（只能證偽）</li>
<li>驗收者無法勾選，ticket 永遠完不成或隨意完成</li>
</ul>
<p><strong>改善</strong>：每條都要能「勾得下去」：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">acceptance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span>- <span class="s2">&#34;[ ] 登入完成時間 p95 從 3.2s 降至 ≤ 1.5s（1 週平均）&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span>- <span class="s2">&#34;[ ] Linter 無新增 warning；cyclomatic complexity 不增加&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span>- <span class="s2">&#34;[ ] 迴歸測試套件（237 個）全數通過，且新增至少 6 個測試&#34;</span></span></span></code></pre></div><hr>
<h2 id="速查表">速查表</h2>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>常見混淆模式</th>
          <th>判斷快問</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>what</code></td>
          <td>把 <code>why</code>（動機）塞進來，用「因為&hellip;所以&hellip;」開頭</td>
          <td>有「因為」→ 搬到 <code>why</code></td>
      </tr>
      <tr>
          <td><code>why</code></td>
          <td>把 <code>what</code>（動作）和 <code>how</code>（實作）寫進來</td>
          <td>有動詞動作 → 搬到 <code>what</code> 或 <code>how</code></td>
      </tr>
      <tr>
          <td><code>when</code></td>
          <td>描述產品行為時序（用戶點了什麼），不是 ticket 啟動條件</td>
          <td>主詞是「用戶」→ 可能寫錯了</td>
      </tr>
      <tr>
          <td><code>where</code></td>
          <td>寫「所有相關地方」等抽象描述，不列具體檔案</td>
          <td>沒有路徑 → 補具體清單</td>
      </tr>
      <tr>
          <td><code>how</code></td>
          <td>把驗收條件（pass/fail 判斷）混入實作計畫</td>
          <td>有「要通過」→ 搬到 <code>acceptance</code></td>
      </tr>
      <tr>
          <td><code>acceptance</code></td>
          <td>寫「變好」「提升」「沒有新問題」等不可驗證描述</td>
          <td>無法勾選 → 加量化指標或可觀察證據</td>
      </tr>
  </tbody>
</table>
<hr>
<p><strong>來源</strong>：從 <code>designing-fields.md</code> Ticket 六欄位角度解析段獨立拆出，保留全部詳細範例
<strong>Last Updated</strong>: 2026-04-18
<strong>Version</strong>: 1.0.0</p>
]]></content:encoded></item><item><title>Dry-run Guide — Skill 發布前語意層驗收</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/dry-run-guide/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/dry-run-guide/</guid><description>&lt;p>本文件定義 compositional-writing Skill 的 Phase 2 語意層驗收流程。Phase 1 是自動化形式掃描（portability-check.sh），Phase 2 是語意層驗收——驗證一個不熟悉本框架的新使用者，只讀 SKILL.md 和對應 reference 後，能否獨立套用寫作方法論。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Phase 2 的核心問題&lt;/strong>：形式合規不代表內容可理解。本流程解答「Skill 內容品質是否足以讓陌生讀者自立運作」。&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>Skill 發布至 marketplace 前&lt;/td>
 &lt;td>必須執行，作為最終品質門&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>新增或大幅修改 reference 後&lt;/td>
 &lt;td>必須針對變動的 reference 跑對應場景&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>meta-metrics.md 顯示 M2 疑慮&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;hr>
&lt;h2 id="前置條件">前置條件&lt;/h2>
&lt;p>執行 Phase 2 前，以下條件必須已成立：&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Phase 1 通過：&lt;code>./scripts/portability-check.sh&lt;/code> exit 0&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> SKILL.md 和所有核心 reference 均已完成（writing-code-comments / writing-documents / writing-logs / writing-prompts / writing-articles / translation-review / designing-fields）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> meta-metrics.md 已完成（M1-M2 量測方式已定義）&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="測試人員要求">測試人員要求&lt;/h2>
&lt;p>Phase 2 測試人員必須符合：&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>對本框架陌生&lt;/td>
 &lt;td>未曾閱讀過 &lt;code>.claude/&lt;/code> 目錄任何框架規則&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>有基礎寫作能力&lt;/td>
 &lt;td>能寫 markdown，有寫程式碼或文件的基本經驗&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可取用的資料&lt;/td>
 &lt;td>僅限 SKILL.md + 指定 reference（不可額外 Google、詢問他人）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>若找不到完全陌生的測試人員，可使用「情境隔離法」：測試者在全新 session 中（不帶任何框架記憶），只閱讀指定文件後完成場景。&lt;/p>
&lt;hr>
&lt;h2 id="測試場景3-個基本場景">測試場景（3 個基本場景）&lt;/h2>
&lt;p>每個場景設計原則：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>輸入&lt;/strong>：只給測試人員 SKILL.md + 1 個對應 reference&lt;/li>
&lt;li>&lt;strong>任務&lt;/strong>：測試人員獨立完成寫作任務&lt;/li>
&lt;li>&lt;strong>時限&lt;/strong>：每個場景 10 分鐘&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="場景-a撰寫程式碼-doc-comment">場景 A：撰寫程式碼 doc comment&lt;/h3>
&lt;p>&lt;strong>對應 reference&lt;/strong>：&lt;code>writing-code-comments.md&lt;/code>&lt;/p>
&lt;h4 id="任務描述給測試人員的指示">任務描述（給測試人員的指示）&lt;/h4>
&lt;p>你有一個 JavaScript 函式，功能是「驗證使用者輸入的書名是否符合格式（非空、長度 1-200 字元）」。&lt;/p>
&lt;p>請只閱讀 SKILL.md 和 &lt;code>writing-code-comments.md&lt;/code>，為以下函式撰寫一則 doc comment：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">validateBookTitle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">title&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">2&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">title&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">title&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">trim&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="kc">false&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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">title&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">trim&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="kc">false&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">return&lt;/span> &lt;span class="kc">true&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="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="輸入限制">輸入限制&lt;/h4>
&lt;ul>
&lt;li>可讀：SKILL.md、references/writing-code-comments.md&lt;/li>
&lt;li>不可讀：其他任何文件&lt;/li>
&lt;/ul>
&lt;h4 id="預期產出">預期產出&lt;/h4>
&lt;p>符合以下條件的 doc comment：&lt;/p>
&lt;ul>
&lt;li>說明業務意圖（為什麼驗證，不是程式碼在做什麼）&lt;/li>
&lt;li>說明輸入語意和邊界條件&lt;/li>
&lt;li>說明回傳值的含義&lt;/li>
&lt;li>不描述實作語法&lt;/li>
&lt;/ul>
&lt;h4 id="評估標準">評估標準&lt;/h4>
&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>comment 說明「為何」而非「如何」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>原子化&lt;/td>
 &lt;td>comment 只解釋此函式的一個職責&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>輸入輸出語意&lt;/td>
 &lt;td>明確說明 &lt;code>title&lt;/code> 的合法格式與 &lt;code>false&lt;/code> 的意義&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>無多餘翻譯&lt;/td>
 &lt;td>不出現「如果 title 為空則回傳 false」這類程式碼翻譯句&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>通過判定&lt;/strong>：4 項中達到 3 項以上 = 通過。&lt;/p>
&lt;hr>
&lt;h3 id="場景-b起草技術文件段落">場景 B：起草技術文件段落&lt;/h3>
&lt;p>&lt;strong>對應 reference&lt;/strong>：&lt;code>writing-documents.md&lt;/code>&lt;/p>
&lt;h4 id="任務描述給測試人員的指示-1">任務描述（給測試人員的指示）&lt;/h4>
&lt;p>你需要為一份「書庫同步錯誤處理設計」文件撰寫開頭章節（Introduction 或 Overview）。&lt;/p>
&lt;p>背景資訊（只有這些）：&lt;/p>
&lt;ul>
&lt;li>系統名稱：Readmoo 書庫管理器&lt;/li>
&lt;li>問題：書庫同步會遇到網路中斷、資料格式不符、API 限流三種錯誤&lt;/li>
&lt;li>讀者：接手此模組的開發者（假設有基礎 JavaScript 能力，不熟悉 Readmoo 平台）&lt;/li>
&lt;/ul>
&lt;p>請只閱讀 SKILL.md 和 &lt;code>writing-documents.md&lt;/code>，撰寫一個 150-300 字的開頭段落，讓讀者第一眼就知道「這份文件解決什麼問題、讀它能得到什麼」。&lt;/p>
&lt;h4 id="輸入限制-1">輸入限制&lt;/h4>
&lt;ul>
&lt;li>可讀：SKILL.md、references/writing-documents.md&lt;/li>
&lt;li>不可讀：其他任何文件&lt;/li>
&lt;/ul>
&lt;h4 id="預期產出-1">預期產出&lt;/h4>
&lt;p>符合以下條件的開頭段落：&lt;/p>
&lt;ul>
&lt;li>第一句說清楚文件解決的問題（不從背景鋪陳）&lt;/li>
&lt;li>指明讀者是誰&lt;/li>
&lt;li>列出文件涵蓋的三種錯誤類型&lt;/li>
&lt;li>不超過 300 字&lt;/li>
&lt;/ul>
&lt;h4 id="評估標準-1">評估標準&lt;/h4>
&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>讀者定位&lt;/td>
 &lt;td>明確說明目標讀者是誰&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>範圍聲明&lt;/td>
 &lt;td>列出本文件涵蓋的三種錯誤類型&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>精簡&lt;/td>
 &lt;td>不超過 300 字、無填充詞（「在這份文件中，我們將&amp;hellip;」）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>通過判定&lt;/strong>：4 項中達到 3 項以上 = 通過。&lt;/p></description><content:encoded><![CDATA[<p>本文件定義 compositional-writing Skill 的 Phase 2 語意層驗收流程。Phase 1 是自動化形式掃描（portability-check.sh），Phase 2 是語意層驗收——驗證一個不熟悉本框架的新使用者，只讀 SKILL.md 和對應 reference 後，能否獨立套用寫作方法論。</p>
<blockquote>
<p><strong>Phase 2 的核心問題</strong>：形式合規不代表內容可理解。本流程解答「Skill 內容品質是否足以讓陌生讀者自立運作」。</p></blockquote>
<hr>
<h2 id="適用時機">適用時機</h2>
<table>
  <thead>
      <tr>
          <th>觸發情境</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Skill 發布至 marketplace 前</td>
          <td>必須執行，作為最終品質門</td>
      </tr>
      <tr>
          <td>新增或大幅修改 reference 後</td>
          <td>必須針對變動的 reference 跑對應場景</td>
      </tr>
      <tr>
          <td>meta-metrics.md 顯示 M2 疑慮</td>
          <td>以本流程做進一步驗證</td>
      </tr>
      <tr>
          <td>定期健檢（每季）</td>
          <td>建議執行，確認內容未因周邊框架演進而失效</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="前置條件">前置條件</h2>
<p>執行 Phase 2 前，以下條件必須已成立：</p>
<ul>
<li><input disabled="" type="checkbox"> Phase 1 通過：<code>./scripts/portability-check.sh</code> exit 0</li>
<li><input disabled="" type="checkbox"> SKILL.md 和所有核心 reference 均已完成（writing-code-comments / writing-documents / writing-logs / writing-prompts / writing-articles / translation-review / designing-fields）</li>
<li><input disabled="" type="checkbox"> meta-metrics.md 已完成（M1-M2 量測方式已定義）</li>
</ul>
<hr>
<h2 id="測試人員要求">測試人員要求</h2>
<p>Phase 2 測試人員必須符合：</p>
<table>
  <thead>
      <tr>
          <th>要求</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>對本框架陌生</td>
          <td>未曾閱讀過 <code>.claude/</code> 目錄任何框架規則</td>
      </tr>
      <tr>
          <td>有基礎寫作能力</td>
          <td>能寫 markdown，有寫程式碼或文件的基本經驗</td>
      </tr>
      <tr>
          <td>可取用的資料</td>
          <td>僅限 SKILL.md + 指定 reference（不可額外 Google、詢問他人）</td>
      </tr>
  </tbody>
</table>
<p>若找不到完全陌生的測試人員，可使用「情境隔離法」：測試者在全新 session 中（不帶任何框架記憶），只閱讀指定文件後完成場景。</p>
<hr>
<h2 id="測試場景3-個基本場景">測試場景（3 個基本場景）</h2>
<p>每個場景設計原則：</p>
<ul>
<li><strong>輸入</strong>：只給測試人員 SKILL.md + 1 個對應 reference</li>
<li><strong>任務</strong>：測試人員獨立完成寫作任務</li>
<li><strong>時限</strong>：每個場景 10 分鐘</li>
</ul>
<hr>
<h3 id="場景-a撰寫程式碼-doc-comment">場景 A：撰寫程式碼 doc comment</h3>
<p><strong>對應 reference</strong>：<code>writing-code-comments.md</code></p>
<h4 id="任務描述給測試人員的指示">任務描述（給測試人員的指示）</h4>
<p>你有一個 JavaScript 函式，功能是「驗證使用者輸入的書名是否符合格式（非空、長度 1-200 字元）」。</p>
<p>請只閱讀 SKILL.md 和 <code>writing-code-comments.md</code>，為以下函式撰寫一則 doc comment：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">function</span> <span class="nx">validateBookTitle</span><span class="p">(</span><span class="nx">title</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">title</span> <span class="o">||</span> <span class="nx">title</span><span class="p">.</span><span class="nx">trim</span><span class="p">().</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">title</span><span class="p">.</span><span class="nx">trim</span><span class="p">().</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">200</span><span class="p">)</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">return</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="p">}</span></span></span></code></pre></div><h4 id="輸入限制">輸入限制</h4>
<ul>
<li>可讀：SKILL.md、references/writing-code-comments.md</li>
<li>不可讀：其他任何文件</li>
</ul>
<h4 id="預期產出">預期產出</h4>
<p>符合以下條件的 doc comment：</p>
<ul>
<li>說明業務意圖（為什麼驗證，不是程式碼在做什麼）</li>
<li>說明輸入語意和邊界條件</li>
<li>說明回傳值的含義</li>
<li>不描述實作語法</li>
</ul>
<h4 id="評估標準">評估標準</h4>
<table>
  <thead>
      <tr>
          <th>評估項目</th>
          <th>通過條件</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>意圖顯性</td>
          <td>comment 說明「為何」而非「如何」</td>
      </tr>
      <tr>
          <td>原子化</td>
          <td>comment 只解釋此函式的一個職責</td>
      </tr>
      <tr>
          <td>輸入輸出語意</td>
          <td>明確說明 <code>title</code> 的合法格式與 <code>false</code> 的意義</td>
      </tr>
      <tr>
          <td>無多餘翻譯</td>
          <td>不出現「如果 title 為空則回傳 false」這類程式碼翻譯句</td>
      </tr>
  </tbody>
</table>
<p><strong>通過判定</strong>：4 項中達到 3 項以上 = 通過。</p>
<hr>
<h3 id="場景-b起草技術文件段落">場景 B：起草技術文件段落</h3>
<p><strong>對應 reference</strong>：<code>writing-documents.md</code></p>
<h4 id="任務描述給測試人員的指示-1">任務描述（給測試人員的指示）</h4>
<p>你需要為一份「書庫同步錯誤處理設計」文件撰寫開頭章節（Introduction 或 Overview）。</p>
<p>背景資訊（只有這些）：</p>
<ul>
<li>系統名稱：Readmoo 書庫管理器</li>
<li>問題：書庫同步會遇到網路中斷、資料格式不符、API 限流三種錯誤</li>
<li>讀者：接手此模組的開發者（假設有基礎 JavaScript 能力，不熟悉 Readmoo 平台）</li>
</ul>
<p>請只閱讀 SKILL.md 和 <code>writing-documents.md</code>，撰寫一個 150-300 字的開頭段落，讓讀者第一眼就知道「這份文件解決什麼問題、讀它能得到什麼」。</p>
<h4 id="輸入限制-1">輸入限制</h4>
<ul>
<li>可讀：SKILL.md、references/writing-documents.md</li>
<li>不可讀：其他任何文件</li>
</ul>
<h4 id="預期產出-1">預期產出</h4>
<p>符合以下條件的開頭段落：</p>
<ul>
<li>第一句說清楚文件解決的問題（不從背景鋪陳）</li>
<li>指明讀者是誰</li>
<li>列出文件涵蓋的三種錯誤類型</li>
<li>不超過 300 字</li>
</ul>
<h4 id="評估標準-1">評估標準</h4>
<table>
  <thead>
      <tr>
          <th>評估項目</th>
          <th>通過條件</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>意圖前置</td>
          <td>第一句即說明文件解決的問題，不先鋪背景</td>
      </tr>
      <tr>
          <td>讀者定位</td>
          <td>明確說明目標讀者是誰</td>
      </tr>
      <tr>
          <td>範圍聲明</td>
          <td>列出本文件涵蓋的三種錯誤類型</td>
      </tr>
      <tr>
          <td>精簡</td>
          <td>不超過 300 字、無填充詞（「在這份文件中，我們將&hellip;」）</td>
      </tr>
  </tbody>
</table>
<p><strong>通過判定</strong>：4 項中達到 3 項以上 = 通過。</p>
<hr>
<h3 id="場景-c設計-yaml-欄位命名">場景 C：設計 YAML 欄位命名</h3>
<p><strong>對應 reference</strong>：<code>designing-fields.md</code></p>
<h4 id="任務描述給測試人員的指示-2">任務描述（給測試人員的指示）</h4>
<p>你正在設計一份 YAML 格式的「書籍匯出任務」結構，需要包含以下資訊：</p>
<ol>
<li>任務的唯一識別碼</li>
<li>任務被建立的原因（是使用者手動觸發，還是排程自動觸發）</li>
<li>任務的目標輸出格式（CSV 或 JSON）</li>
<li>任務目前的執行狀態（待執行、執行中、完成、失敗）</li>
<li>任務完成後通知的方式（email 或 webhook）</li>
</ol>
<p>請只閱讀 SKILL.md 和 <code>designing-fields.md</code>，設計上述 5 個欄位的名稱與值的格式（用 YAML 示例呈現即可）。</p>
<h4 id="輸入限制-2">輸入限制</h4>
<ul>
<li>可讀：SKILL.md、references/designing-fields.md</li>
<li>不可讀：其他任何文件</li>
</ul>
<h4 id="預期產出-2">預期產出</h4>
<p>一段 YAML 示例，每個欄位有名稱和示例值，並附一行說明「此欄位承載什麼維度的資訊」。</p>
<h4 id="評估標準-2">評估標準</h4>
<table>
  <thead>
      <tr>
          <th>評估項目</th>
          <th>通過條件</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>欄位職責單一</td>
          <td>每個欄位只描述一個維度，無複合欄位（如 <code>trigger_and_format</code>）</td>
      </tr>
      <tr>
          <td>名稱即提問</td>
          <td>欄位名稱讀者能猜到「它在問什麼問題」</td>
      </tr>
      <tr>
          <td>值格式一致</td>
          <td>狀態類欄位使用 enum（固定可選值），不用自由文字</td>
      </tr>
      <tr>
          <td>grep 友善</td>
          <td>欄位名稱無縮寫、蛇形命名（如 <code>output_format</code> 非 <code>outFmt</code>）</td>
      </tr>
  </tbody>
</table>
<p><strong>通過判定</strong>：4 項中達到 3 項以上 = 通過。</p>
<hr>
<h2 id="通過標準量化">通過標準（量化）</h2>
<p>整體 Phase 2 通過條件：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>通過值</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>場景通過率</td>
          <td>100%（3/3 場景）</td>
          <td>任一場景未通過即整體 FAIL</td>
      </tr>
      <tr>
          <td>單場景完成時間</td>
          <td>10 分鐘內</td>
          <td>超時視為理解障礙，需調查根因</td>
      </tr>
      <tr>
          <td>場景內評估項通過率</td>
          <td>75% 以上（至少 3/4 項）</td>
          <td>低於此值視為該場景 FAIL</td>
      </tr>
  </tbody>
</table>
<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">Phase 2 通過 = (所有場景通過) AND (所有場景在 10 分鐘內完成)
</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">場景通過 = 評估項通過率 &gt;= 75%</span></span></code></pre></div><hr>
<h2 id="評估問卷測試後請測試人員回答">評估問卷（測試後請測試人員回答）</h2>
<p>每個場景完成後，請測試人員回答以下問題（1-5 分，5 分最高）：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>目的</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Q1. SKILL.md 的觸發路由表讓你清楚知道要讀哪個 reference 嗎？</td>
          <td>驗證 M1 路徑設計</td>
      </tr>
      <tr>
          <td>Q2. 你讀完指定 reference 後，能獨立完成任務而不需要其他資料嗎？</td>
          <td>驗證 M2 獨立理解率</td>
      </tr>
      <tr>
          <td>Q3. reference 中是否有你不理解的名詞或概念？（有請列出）</td>
          <td>發現就地定義缺失</td>
      </tr>
      <tr>
          <td>Q4. 如果你要把本方法論教給同事，你覺得這份文件夠完整嗎？</td>
          <td>整體可遷移性</td>
      </tr>
  </tbody>
</table>
<p><strong>問卷通過標準</strong>：Q1 + Q2 平均分 &gt;= 4 分，Q3 無未解答的名詞。</p>
<hr>
<h2 id="執行步驟">執行步驟</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">1. 確認前置條件：執行 ./scripts/portability-check.sh，exit 0 才繼續
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 找到測試人員（符合「對本框架陌生」條件）
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 依序執行場景 A → B → C，記錄每個場景的完成時間和評估項結果
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 場景結束後請測試人員完成評估問卷
</span></span><span class="line"><span class="ln">5</span><span class="cl">5. 計算通過率（場景 + 問卷）
</span></span><span class="line"><span class="ln">6</span><span class="cl">6. 填寫下方結果記錄表
</span></span><span class="line"><span class="ln">7</span><span class="cl">7. 若有場景 FAIL，記錄根因並提交修正 Ticket</span></span></code></pre></div><hr>
<h2 id="結果記錄表執行後填寫">結果記錄表（執行後填寫）</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">Phase 2 Dry-Run 結果（日期：_____ / 執行者：_____）
</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">| 場景 | 完成時間 | 評估項通過數 | 通過判定 |
</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：程式碼 doc comment | ___ 分鐘 | ___/4 | PASS / FAIL |
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">| B：技術文件段落 | ___ 分鐘 | ___/4 | PASS / FAIL |
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">| C：YAML 欄位設計 | ___ 分鐘 | ___/4 | PASS / FAIL |
</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">
</span></span><span class="line"><span class="ln">13</span><span class="cl">| 問題 | 分數（1-5） | 備註 |
</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">| Q1 觸發路由清晰度 | ___ | |
</span></span><span class="line"><span class="ln">16</span><span class="cl">| Q2 reference 獨立理解率 | ___ | |
</span></span><span class="line"><span class="ln">17</span><span class="cl">| Q3 不理解的名詞 | N/A（列出：_____） | |
</span></span><span class="line"><span class="ln">18</span><span class="cl">| Q4 整體可遷移性 | ___ | |
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">整體結論：PASS / FAIL
</span></span><span class="line"><span class="ln">21</span><span class="cl">FAIL 時的修正清單：
</span></span><span class="line"><span class="ln">22</span><span class="cl">- （列出需要修正的 reference 或章節）</span></span></code></pre></div><hr>
<h2 id="常見失敗根因與對應修正">常見失敗根因與對應修正</h2>
<table>
  <thead>
      <tr>
          <th>症狀</th>
          <th>根因</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>測試人員完成時間 &gt; 10 分鐘</td>
          <td>reference 資訊密度過高或結構不清</td>
          <td>重新組織 reference，讓 TL;DR 或 Quick Reference 更突出</td>
      </tr>
      <tr>
          <td>測試人員需要查閱第二個 reference</td>
          <td>reference 未自包含（M2 FAIL）</td>
          <td>把被引用內容的精要直接寫入當前 reference</td>
      </tr>
      <tr>
          <td>評估項「意圖顯性」通過率低</td>
          <td>SKILL.md 對「意圖顯性」的定義不夠具體</td>
          <td>在對應 reference 加入更多示例（正例 + 反例）</td>
      </tr>
      <tr>
          <td>問卷 Q3 列出未解答名詞</td>
          <td>首次出現的術語缺少就地定義</td>
          <td>在術語首次出現處加入一行定義（括號說明或 note block）</td>
      </tr>
      <tr>
          <td>場景 C 欄位設計評估項通過率低</td>
          <td>designing-fields.md 的「欄位名稱即提問」原則不夠清楚</td>
          <td>在該原則下加更多反例對照（好名稱 vs 壞名稱）</td>
      </tr>
  </tbody>
</table>
<hr>
<p><strong>Last Updated</strong>: 2026-04-18
<strong>Version</strong>: 1.0.0</p>
]]></content:encoded></item><item><title>Meta Metrics — 寫作品質量化驗收</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/meta-metrics/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/meta-metrics/</guid><description>&lt;p>本 reference 定義 compositional-writing Skill 的品質量化驗收方式。寫得好不好是主觀判斷，metric 讓它變成可重複驗證的客觀評估。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>核心理念&lt;/strong>：Metric 是自評工具，不是硬性 KPI。目的是讓「達成/未達成」可被重複驗證，並在 Skill 演化過程中守住設計底線。&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>驗收本 Skill 新增或修改的 reference&lt;/td>
 &lt;td>跑 M1-M2 檢查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>決定是否拆分/合併 reference&lt;/td>
 &lt;td>用 M2 評估獨立理解率&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>懷疑 SKILL.md 路由設計失靈&lt;/td>
 &lt;td>用 M1 跑 6 情境預期路徑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Skill 發布前的品質門&lt;/td>
 &lt;td>M1 + M2 全綠才允許發布（M3-M5 待定後納入）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="metric-總覽">Metric 總覽&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>ID&lt;/th>
 &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>M1&lt;/td>
 &lt;td>找到答案路徑長度&lt;/td>
 &lt;td>認知負擔&lt;/td>
 &lt;td>≤ 2 個檔案&lt;/td>
 &lt;td>6 情境預期路徑對照&lt;/td>
 &lt;td>正式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>M2&lt;/td>
 &lt;td>reference 獨立理解率&lt;/td>
 &lt;td>認知負擔&lt;/td>
 &lt;td>100%&lt;/td>
 &lt;td>逐 reference 獨立閱讀檢查&lt;/td>
 &lt;td>正式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>M3&lt;/td>
 &lt;td>SKILL.md 主檔 token 上限&lt;/td>
 &lt;td>Token&lt;/td>
 &lt;td>≤ 5000 tokens（候選）&lt;/td>
 &lt;td>Tokenizer 計數&lt;/td>
 &lt;td>未決（待實際範例後定）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>M4&lt;/td>
 &lt;td>單一 reference 觸發載入 token&lt;/td>
 &lt;td>Token&lt;/td>
 &lt;td>≤ 3000 tokens（候選）&lt;/td>
 &lt;td>Tokenizer 計數&lt;/td>
 &lt;td>未決（待實際範例後定）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>M5&lt;/td>
 &lt;td>grep 定位問題的 token 成本&lt;/td>
 &lt;td>Token&lt;/td>
 &lt;td>&amp;lt; 500 tokens（候選）&lt;/td>
 &lt;td>grep + tokenizer 實測&lt;/td>
 &lt;td>未決（待實際範例後定）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>認知負擔類（M1-M2）為本版正式 metric；Token 類（M3-M5）為候選，待 references/ 內容穩定後再定最終閾值。&lt;/p>
&lt;hr>
&lt;h2 id="m1--找到答案路徑長度">M1 — 找到答案路徑長度&lt;/h2>
&lt;h3 id="定義">定義&lt;/h3>
&lt;p>讀者帶著一個寫作問題從 SKILL.md 開始，&lt;strong>需要開啟幾個檔案&lt;/strong>才能解決問題。路徑長度 = SKILL.md 外額外開啟的 reference 檔案數。&lt;/p>
&lt;ul>
&lt;li>路徑 1：只讀 SKILL.md 就解決（極罕見，只適用於速查表即可回答的問題）&lt;/li>
&lt;li>路徑 2：SKILL.md 指引 → 開啟 1 個 reference（預期主流）&lt;/li>
&lt;li>路徑 3+：SKILL.md → reference A → reference B（違反本 metric）&lt;/li>
&lt;/ul>
&lt;h3 id="目標值">目標值&lt;/h3>
&lt;p>&lt;strong>≤ 2&lt;/strong>（SKILL.md + 1 個 reference 即解決）。&lt;/p>
&lt;h3 id="量測方式">量測方式&lt;/h3>
&lt;p>針對本 Skill 支援的 6 個觸發情境，逐一演練：&lt;/p>
&lt;ol>
&lt;li>模擬讀者帶著該情境的問題進入 SKILL.md&lt;/li>
&lt;li>依照 SKILL.md 觸發路由表選擇 reference&lt;/li>
&lt;li>計算需開啟的檔案總數（含 SKILL.md）&lt;/li>
&lt;li>任一情境路徑長度 &amp;gt; 2 即 M1 FAIL&lt;/li>
&lt;/ol>
&lt;h3 id="6-個觸發情境的預期路徑">6 個觸發情境的預期路徑&lt;/h3>
&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>1&lt;/td>
 &lt;td>要寫一段程式碼註解&lt;/td>
 &lt;td>「這個函式的註解該寫什麼？doc comment 和 inline 要怎麼選？」&lt;/td>
 &lt;td>SKILL.md → writing-code-comments.md&lt;/td>
 &lt;td>2&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2&lt;/td>
 &lt;td>要起草一份文件（worklog/markdown）&lt;/td>
 &lt;td>「章節怎麼切？哪些資訊該放、哪些不該放？」&lt;/td>
 &lt;td>SKILL.md → writing-documents.md&lt;/td>
 &lt;td>2&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3&lt;/td>
 &lt;td>要設計 log 訊息&lt;/td>
 &lt;td>「這個 log 要寫什麼關鍵字才搜得到？」&lt;/td>
 &lt;td>SKILL.md → writing-logs.md&lt;/td>
 &lt;td>2&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4&lt;/td>
 &lt;td>要撰寫 prompt&lt;/td>
 &lt;td>「怎麼讓 prompt 更省 token 又講清楚意圖？」&lt;/td>
 &lt;td>SKILL.md → writing-prompts.md&lt;/td>
 &lt;td>2&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>5&lt;/td>
 &lt;td>要設計 ticket/schema 欄位&lt;/td>
 &lt;td>「what 和 why 欄位怎麼分？frontmatter 要有哪些欄位？」&lt;/td>
 &lt;td>SKILL.md → designing-fields.md&lt;/td>
 &lt;td>2&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>6&lt;/td>
 &lt;td>要驗證寫作品質（自評）&lt;/td>
 &lt;td>「我寫完了，怎麼知道有沒有達標？」&lt;/td>
 &lt;td>SKILL.md → meta-metrics.md&lt;/td>
 &lt;td>2&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>預期總平均&lt;/strong>：2（所有情境都是 SKILL.md + 1 個 reference）。&lt;/p></description><content:encoded><![CDATA[<p>本 reference 定義 compositional-writing Skill 的品質量化驗收方式。寫得好不好是主觀判斷，metric 讓它變成可重複驗證的客觀評估。</p>
<blockquote>
<p><strong>核心理念</strong>：Metric 是自評工具，不是硬性 KPI。目的是讓「達成/未達成」可被重複驗證，並在 Skill 演化過程中守住設計底線。</p></blockquote>
<hr>
<h2 id="何時參閱本檔">何時參閱本檔</h2>
<table>
  <thead>
      <tr>
          <th>觸發情境</th>
          <th>動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>驗收本 Skill 新增或修改的 reference</td>
          <td>跑 M1-M2 檢查</td>
      </tr>
      <tr>
          <td>決定是否拆分/合併 reference</td>
          <td>用 M2 評估獨立理解率</td>
      </tr>
      <tr>
          <td>懷疑 SKILL.md 路由設計失靈</td>
          <td>用 M1 跑 6 情境預期路徑</td>
      </tr>
      <tr>
          <td>Skill 發布前的品質門</td>
          <td>M1 + M2 全綠才允許發布（M3-M5 待定後納入）</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="metric-總覽">Metric 總覽</h2>
<table>
  <thead>
      <tr>
          <th>ID</th>
          <th>名稱</th>
          <th>類別</th>
          <th>目標值</th>
          <th>量測方式</th>
          <th>狀態</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>M1</td>
          <td>找到答案路徑長度</td>
          <td>認知負擔</td>
          <td>≤ 2 個檔案</td>
          <td>6 情境預期路徑對照</td>
          <td>正式</td>
      </tr>
      <tr>
          <td>M2</td>
          <td>reference 獨立理解率</td>
          <td>認知負擔</td>
          <td>100%</td>
          <td>逐 reference 獨立閱讀檢查</td>
          <td>正式</td>
      </tr>
      <tr>
          <td>M3</td>
          <td>SKILL.md 主檔 token 上限</td>
          <td>Token</td>
          <td>≤ 5000 tokens（候選）</td>
          <td>Tokenizer 計數</td>
          <td>未決（待實際範例後定）</td>
      </tr>
      <tr>
          <td>M4</td>
          <td>單一 reference 觸發載入 token</td>
          <td>Token</td>
          <td>≤ 3000 tokens（候選）</td>
          <td>Tokenizer 計數</td>
          <td>未決（待實際範例後定）</td>
      </tr>
      <tr>
          <td>M5</td>
          <td>grep 定位問題的 token 成本</td>
          <td>Token</td>
          <td>&lt; 500 tokens（候選）</td>
          <td>grep + tokenizer 實測</td>
          <td>未決（待實際範例後定）</td>
      </tr>
  </tbody>
</table>
<p>認知負擔類（M1-M2）為本版正式 metric；Token 類（M3-M5）為候選，待 references/ 內容穩定後再定最終閾值。</p>
<hr>
<h2 id="m1--找到答案路徑長度">M1 — 找到答案路徑長度</h2>
<h3 id="定義">定義</h3>
<p>讀者帶著一個寫作問題從 SKILL.md 開始，<strong>需要開啟幾個檔案</strong>才能解決問題。路徑長度 = SKILL.md 外額外開啟的 reference 檔案數。</p>
<ul>
<li>路徑 1：只讀 SKILL.md 就解決（極罕見，只適用於速查表即可回答的問題）</li>
<li>路徑 2：SKILL.md 指引 → 開啟 1 個 reference（預期主流）</li>
<li>路徑 3+：SKILL.md → reference A → reference B（違反本 metric）</li>
</ul>
<h3 id="目標值">目標值</h3>
<p><strong>≤ 2</strong>（SKILL.md + 1 個 reference 即解決）。</p>
<h3 id="量測方式">量測方式</h3>
<p>針對本 Skill 支援的 6 個觸發情境，逐一演練：</p>
<ol>
<li>模擬讀者帶著該情境的問題進入 SKILL.md</li>
<li>依照 SKILL.md 觸發路由表選擇 reference</li>
<li>計算需開啟的檔案總數（含 SKILL.md）</li>
<li>任一情境路徑長度 &gt; 2 即 M1 FAIL</li>
</ol>
<h3 id="6-個觸發情境的預期路徑">6 個觸發情境的預期路徑</h3>
<table>
  <thead>
      <tr>
          <th>#</th>
          <th>觸發情境</th>
          <th>典型讀者問題</th>
          <th>預期路徑</th>
          <th>路徑長度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>要寫一段程式碼註解</td>
          <td>「這個函式的註解該寫什麼？doc comment 和 inline 要怎麼選？」</td>
          <td>SKILL.md → writing-code-comments.md</td>
          <td>2</td>
      </tr>
      <tr>
          <td>2</td>
          <td>要起草一份文件（worklog/markdown）</td>
          <td>「章節怎麼切？哪些資訊該放、哪些不該放？」</td>
          <td>SKILL.md → writing-documents.md</td>
          <td>2</td>
      </tr>
      <tr>
          <td>3</td>
          <td>要設計 log 訊息</td>
          <td>「這個 log 要寫什麼關鍵字才搜得到？」</td>
          <td>SKILL.md → writing-logs.md</td>
          <td>2</td>
      </tr>
      <tr>
          <td>4</td>
          <td>要撰寫 prompt</td>
          <td>「怎麼讓 prompt 更省 token 又講清楚意圖？」</td>
          <td>SKILL.md → writing-prompts.md</td>
          <td>2</td>
      </tr>
      <tr>
          <td>5</td>
          <td>要設計 ticket/schema 欄位</td>
          <td>「what 和 why 欄位怎麼分？frontmatter 要有哪些欄位？」</td>
          <td>SKILL.md → designing-fields.md</td>
          <td>2</td>
      </tr>
      <tr>
          <td>6</td>
          <td>要驗證寫作品質（自評）</td>
          <td>「我寫完了，怎麼知道有沒有達標？」</td>
          <td>SKILL.md → meta-metrics.md</td>
          <td>2</td>
      </tr>
  </tbody>
</table>
<p><strong>預期總平均</strong>：2（所有情境都是 SKILL.md + 1 個 reference）。</p>
<h3 id="量測通過條件">量測通過條件</h3>
<ul>
<li><input disabled="" type="checkbox"> 6 個情境逐一演練，每個路徑長度 ≤ 2</li>
<li><input disabled="" type="checkbox"> 無需開啟 reference 後再被引導到另一個 reference（禁止鏈式引用）</li>
<li><input disabled="" type="checkbox"> SKILL.md 的觸發路由表涵蓋 6 個情境，且每個情境對應唯一 reference</li>
</ul>
<h3 id="量測失敗的典型訊號">量測失敗的典型訊號</h3>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>根因</th>
          <th>對應修正</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>讀者讀完某 reference 仍需再開第二個 reference</td>
          <td>reference 未自包含，資訊散落</td>
          <td>把缺漏的原則說明直接寫入該 reference</td>
      </tr>
      <tr>
          <td>同一問題在 SKILL.md 路由表找到兩個候選 reference</td>
          <td>情境切分重疊</td>
          <td>重新畫 reference 邊界，情境不重複</td>
      </tr>
      <tr>
          <td>某情境找不到對應 reference</td>
          <td>情境覆蓋不足</td>
          <td>新增 reference 或擴充最近情境的覆蓋範圍</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="m2--reference-獨立理解率">M2 — reference 獨立理解率</h2>
<h3 id="定義-1">定義</h3>
<p>隨機挑選一個 reference，讀者<strong>不看其他 reference、不看 SKILL.md</strong> 的情況下，能否獨立理解該 reference 的完整意義並套用到寫作情境。</p>
<h3 id="目標值-1">目標值</h3>
<p><strong>100%</strong>（每個 reference 都必須能獨立理解）。</p>
<h3 id="量測方式-1">量測方式</h3>
<p>對每個 reference 執行「獨立閱讀檢查」：</p>
<ol>
<li>隨機挑一個 reference（不提供 SKILL.md 和其他 reference）</li>
<li>讀完後自問以下四個問題：
<ul>
<li>我能否說出這份 reference 在講什麼情境？</li>
<li>我能否判斷自己遇到的問題適不適合用這份 reference？</li>
<li>我能否直接套用它給出的原則到我的寫作？</li>
<li>我是否需要去翻其他檔案才能理解某個名詞或規則？</li>
</ul>
</li>
<li>任一問題答案為「否」（第 4 題為「是」也算否）即 M2 FAIL</li>
</ol>
<p><strong>量測注意事項：必須讀實際內文</strong>。大綱式 review（只看標題、目錄、frontmatter 描述）無法判讀：</p>
<ul>
<li>議題是否切了一半（標題用「+」綁兩概念但內文機制完全不同）</li>
<li>語氣是否絕對主義（「正確概念是 X」vs「比較好的做法是 A、因為 [情境]」）</li>
<li>各段落是否服務同一個 focus（內文段落離題、與標題承諾不符）</li>
<li>引用是否有實質說明（「另見 X」vs「具體做法由 X 完整展開、本篇要記住的是 [&hellip;]」）</li>
</ul>
<p>M2 評估必須讀完整內文、不能只看標題與大綱。</p>
<h3 id="量測通過條件-1">量測通過條件</h3>
<ul>
<li><input disabled="" type="checkbox"> 每個 reference 內部有完整的「情境說明」章節，讀者不看 SKILL.md 也能判斷適用性</li>
<li><input disabled="" type="checkbox"> 所有核心原則（原子化、索引、意圖顯性與商業邏輯、可查詢性、欄位設計）在該 reference 需要時<strong>就地說明</strong>（不引用其他 reference）</li>
<li><input disabled="" type="checkbox"> 專有名詞（Zettelkasten/MOC/原子化等）在首次出現時給出就地定義或連結到外部公認資源</li>
<li><input disabled="" type="checkbox"> reference 間無內部交叉引用（禁止 A→B→C 鏈式）</li>
</ul>
<h3 id="獨立理解率計算">獨立理解率計算</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">獨立理解率 = 通過檢查的 reference 數 / reference 總數 × 100%</span></span></code></pre></div><p>目標 100%，任一 reference 失敗即整體 M2 FAIL。</p>
<h3 id="量測失敗的典型訊號-1">量測失敗的典型訊號</h3>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>根因</th>
          <th>對應修正</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>讀者讀完後說「我要先去看另一個 reference 才懂」</td>
          <td>內部交叉引用</td>
          <td>把被引用的原則說明複製/濃縮到本 reference</td>
      </tr>
      <tr>
          <td>讀者無法判斷「這份 reference 講的是什麼情境」</td>
          <td>情境說明章節缺失或模糊</td>
          <td>開頭新增「何時參閱本檔」或「適用情境」章節</td>
      </tr>
      <tr>
          <td>讀者看到專有名詞卡住</td>
          <td>未就地定義</td>
          <td>在首次出現處加一行解釋或外部連結</td>
      </tr>
      <tr>
          <td>大綱式 review 通過、讀內文發現議題切一半 / 語氣絕對主義 / focus 發散</td>
          <td>M2 量測只看標題大綱、沒讀內文</td>
          <td>量測流程加上「讀完整內文」要求；review 標題與內文一致、不能只信任標題承諾</td>
      </tr>
      <tr>
          <td>教某條規則的段落本身違反該規則（dogfooding 失敗）</td>
          <td>reference 寫作沒套用自己教的規則</td>
          <td>寫完每段教學文字後跑「這段教學的規則 X 適用於這段文字本身嗎？適用 → 符合嗎？」自問</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="m3--skillmd-主檔-token-上限候選未決">M3 — SKILL.md 主檔 token 上限（候選，未決）</h2>
<h3 id="定義-2">定義</h3>
<p>SKILL.md 主檔被 Claude 載入時的 token 消耗總量。</p>
<h3 id="候選目標值">候選目標值</h3>
<p><strong>≤ 5000 tokens</strong>（對齊 Anthropic 官方建議：SKILL.md body &lt; 5k tokens）。</p>
<h3 id="量測方式候選">量測方式（候選）</h3>
<ol>
<li>用 Anthropic tokenizer 或 Claude Code 內建計數計算 SKILL.md 全文 token 數</li>
<li>扣除 YAML frontmatter（frontmatter 常駐 system prompt，另計）</li>
<li>body token 數須 ≤ 5000</li>
</ol>
<h3 id="狀態">狀態</h3>
<p><strong>未決</strong>。實際閾值待 SKILL.md 與各 reference 完成後，量測實際 token 數再定最終值。可能調整方向：</p>
<ul>
<li>若實際 SKILL.md 穩定在 2000 tokens 內，閾值可降為 3000 tokens 作為緩衝</li>
<li>若 SKILL.md 接近 5000 tokens，需拆分更多內容到 references 而非放寬閾值</li>
</ul>
<hr>
<h2 id="m4--單一-reference-觸發載入-token候選未決">M4 — 單一 reference 觸發載入 token（候選，未決）</h2>
<h3 id="定義-3">定義</h3>
<p>單一 reference 被 Claude 按需載入時的 token 消耗量。</p>
<h3 id="候選目標值-1">候選目標值</h3>
<p><strong>≤ 3000 tokens</strong>（避免單一 reference 過大，讓讀者/Claude 只能全讀）。</p>
<h3 id="量測方式候選-1">量測方式（候選）</h3>
<ol>
<li>對每個 reference 計算 token 數</li>
<li>任一 reference &gt; 3000 tokens 即 FAIL</li>
<li>超標的 reference 應檢討：是否混入多個情境？是否能切分？</li>
</ol>
<h3 id="狀態-1">狀態</h3>
<p><strong>未決</strong>。實際閾值待所有 reference 完成後依分佈定案。候選分級：</p>
<ul>
<li>嚴格（&lt; 2000）：強制 reference 高度原子化</li>
<li>寬鬆（&lt; 4000）：允許情境 reference 包含更多實例</li>
<li>最終選擇依 Skill 各 reference 完成後的實測資料定</li>
</ul>
<hr>
<h2 id="m5--grep-定位問題的-token-成本候選未決">M5 — grep 定位問題的 token 成本（候選，未決）</h2>
<h3 id="定義-4">定義</h3>
<p>讀者/AI 用 grep + 關鍵字在 Skill 內定位到答案時，所讀取的 token 總成本。</p>
<h3 id="候選目標值-2">候選目標值</h3>
<p><strong>&lt; 500 tokens</strong>（grep 結果片段 + 關鍵命中行的上下文即能回答問題）。</p>
<h3 id="量測方式候選-2">量測方式（候選）</h3>
<ol>
<li>挑選 10 個代表性寫作問題</li>
<li>用對應關鍵字 grep 全 Skill 內容</li>
<li>取前 N 個命中（N=3），計算閱讀命中上下文所需 token</li>
<li>任一問題 &gt; 500 tokens 即 FAIL</li>
</ol>
<h3 id="狀態-2">狀態</h3>
<p><strong>未決</strong>。此 metric 需要：</p>
<ul>
<li>先建立「代表性問題」清單（10-20 個）</li>
<li>實測 grep 命中品質（關鍵字設計是否到位）</li>
<li>依實測結果決定 500/1000/2000 tokens 哪個是可行閾值</li>
</ul>
<hr>
<h2 id="自評流程">自評流程</h2>
<h3 id="何時執行">何時執行</h3>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>執行的 Metric</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>新增 reference 時</td>
          <td>M1（新增情境路徑）+ M2（新 reference 獨立理解率）</td>
      </tr>
      <tr>
          <td>修改 reference 內容時</td>
          <td>M2（該 reference 獨立理解率）+ M1（若觸發路由變動）</td>
      </tr>
      <tr>
          <td>修改 SKILL.md 路由表時</td>
          <td>M1 全 6 情境重測</td>
      </tr>
      <tr>
          <td>Skill 版本發布前</td>
          <td>M1 + M2 全綠（M3-M5 定案後納入）</td>
      </tr>
      <tr>
          <td>定期健檢（每季）</td>
          <td>全部 metric 重跑</td>
      </tr>
  </tbody>
</table>
<h3 id="由誰執行">由誰執行</h3>
<ul>
<li>撰寫者自評：寫完立即跑 M1 + M2</li>
<li>審查者覆核：Skill PR/Ticket 驗收時跑全 metric</li>
<li>跨專案使用者：在 marketplace 下載 Skill 後，建議跑 M2 確認獨立理解率</li>
</ul>
<h3 id="用什麼工具">用什麼工具</h3>
<table>
  <thead>
      <tr>
          <th>Metric</th>
          <th>工具</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>M1</td>
          <td>手動演練（6 情境對照表）+ 本檔「量測通過條件」清單</td>
      </tr>
      <tr>
          <td>M2</td>
          <td>手動獨立閱讀 + 四問檢查清單</td>
      </tr>
      <tr>
          <td>M3</td>
          <td>Anthropic tokenizer / Claude Code 內建 token 計數</td>
      </tr>
      <tr>
          <td>M4</td>
          <td>同 M3，per-file 計數</td>
      </tr>
      <tr>
          <td>M5</td>
          <td>grep（或 ripgrep）+ tokenizer</td>
      </tr>
  </tbody>
</table>
<h3 id="自評產出格式">自評產出格式</h3>
<p>建議自評結果以下表記錄（貼到 Skill 或相關 ticket 的驗收區）：</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">Metric 自評結果（日期 / 執行者）
</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">| Metric | 結果 | 備註 |
</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">| M1 | PASS (所有情境 ≤ 2) | — |
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">| M2 | PASS (6/6 reference 獨立理解) | — |
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">| M3 | N/A（未決） | SKILL.md 實測 XXXX tokens |
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">| M4 | N/A（未決） | 最大 reference XXXX tokens |
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">| M5 | N/A（未決） | 待關鍵字清單定案 |
</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">整體結論：PASS / FAIL + 須修正項目清單</span></span></code></pre></div><hr>
<h2 id="metric-間的優先級">Metric 間的優先級</h2>
<p>當 metric 之間出現張力時，依下列順序取捨：</p>
<ol>
<li><strong>M2 &gt; M1</strong>：若為了降低路徑長度而讓某 reference 依賴另一個 reference，優先維持獨立理解（M2），重新設計路由而非犧牲獨立性</li>
<li><strong>M1 &gt; M3/M4</strong>：若為了壓 token 而切得太細導致路徑長度 &gt; 2，優先維持 M1，放寬單檔 token 閾值</li>
<li><strong>M2 &gt; M5</strong>：若為了 grep 定位成本而在多處重複同一關鍵說明，優先維持 M2（獨立理解），允許 grep 命中多個檔案但每個命中都能獨立看懂</li>
</ol>
<p><strong>核心排序</strong>：M2 &gt; M1 &gt; M5 &gt; M3 ≈ M4</p>
<p>這個排序反映：獨立理解是 compositional writing 的命脈（卡片盒核心），路徑長度是讀者體驗，token 是工程成本；若三者衝突，優先守住可理解性。</p>
<hr>
<h2 id="與上層原則的對應">與上層原則的對應</h2>
<table>
  <thead>
      <tr>
          <th>Metric</th>
          <th>守護的寫作原則</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>M1</td>
          <td>情境匹配度（一個情境對應一張卡）</td>
      </tr>
      <tr>
          <td>M2</td>
          <td>原子化（一張卡獨立自足）+ 可查詢性（就地定義）</td>
      </tr>
      <tr>
          <td>M3</td>
          <td>精簡至上（Concise is Key）</td>
      </tr>
      <tr>
          <td>M4</td>
          <td>原子化（reference 不過度肥大）</td>
      </tr>
      <tr>
          <td>M5</td>
          <td>可查詢性（關鍵字設計到位）</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="變更歷史">變更歷史</h2>
<table>
  <thead>
      <tr>
          <th>版本</th>
          <th>變更</th>
          <th>日期</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0.1.0</td>
          <td>初版，M1-M2 正式、M3-M5 候選未決</td>
          <td>2026-04-16</td>
      </tr>
      <tr>
          <td>0.2.0</td>
          <td>M2 量測方式補強「必須讀實際內文」注意事項與失敗訊號（從 report folder 50+ 篇 review 經驗回流：大綱式 review 漏了議題切一半、語氣絕對主義、focus 發散等內文層問題）</td>
          <td>2026-04-25</td>
      </tr>
      <tr>
          <td>0.3.0</td>
          <td>M2 失敗訊號加 dogfooding 失敗 — 教某條規則的段落本身違反該規則（從 v0.5.0 / v0.6.0 寫作經驗回流）</td>
          <td>2026-04-25</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>Reference Authoring Standards — Skill reference 撰寫品質規範</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/reference-authoring-standards/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/reference-authoring-standards/</guid><description>&lt;p>本文件為 compositional-writing Skill 的 reference 撰寫品質規範。適用對象：新增或修改本 Skill 任何 reference 的作者（人類或代理人）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。品質驗收 metric（M1-M2）見 &lt;code>meta-metrics.md&lt;/code>；本文件聚焦「如何寫出符合品質的 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>新增一份 reference&lt;/td>
 &lt;td>遵循本文件的結構模板和品質標準&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>修改現有 reference&lt;/td>
 &lt;td>確認改後仍符合五項必要要素&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>審查 Skill PR/Ticket&lt;/td>
 &lt;td>逐項對照本文件的「驗收清單」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>懷疑某 reference 違反原子化&lt;/td>
 &lt;td>執行「職責單一性測試」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="一份合格-reference-的五項必要要素">一份合格 Reference 的五項必要要素&lt;/h2>
&lt;h3 id="要素-1情境聲明何時讀這份文件">要素 1：情境聲明（何時讀這份文件）&lt;/h3>
&lt;p>每份 reference 的開頭必須有明確的「情境說明」段落，讓讀者在 30 秒內判斷適用性。&lt;/p>
&lt;p>&lt;strong>必須回答的問題&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>這份 reference 服務哪種寫作情境？&lt;/li>
&lt;li>讀者帶著什麼問題進來？&lt;/li>
&lt;li>不適用的情境是什麼？&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>範本&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">本 reference 為「撰寫 prompt / instruction / Agent 派發」情境。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">適用：給 AI 模型的指令；Ticket Context Bundle；結構化派發 prompt。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">不適用：文件（見 writing-documents.md）；程式碼註解（見 writing-code-comments.md）。&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>反例&lt;/strong>（情境不明確）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">本文件說明寫作原則的應用。&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="要素-2職責單一性原子化測試">要素 2：職責單一性（原子化測試）&lt;/h3>
&lt;p>每份 reference 只服務一個讀者群體、回答一類問題。&lt;/p>
&lt;p>&lt;strong>判斷方法（原子化測試）&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>必須拆分&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>反例&lt;/strong>（雙重職責）：&lt;/p>
&lt;p>一份 reference 同時說明「如何撰寫 prompt」AND「如何撰寫其他 reference」——兩個讀者群體、兩種產出，必須拆分。&lt;/p>
&lt;hr>
&lt;h3 id="要素-3自包含性m2-通過">要素 3：自包含性（M2 通過）&lt;/h3>
&lt;p>讀者&lt;strong>不看 SKILL.md 和其他 reference&lt;/strong> 的情況下，能獨立理解並套用。&lt;/p>
&lt;p>&lt;strong>自包含清單&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 開頭有情境說明（不依賴 SKILL.md 判斷適用性）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 文件內所有原則在本文件內展開（不引用其他 reference）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 專有名詞在首次出現時給出就地定義&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 無「見 writing-xxx.md」的交叉引用（禁止鏈式）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>可接受的外部引用&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>引用外部公認資源（Anthropic 官方文件、業界標準）&lt;/li>
&lt;li>引用 SKILL.md 觸發路由（只在「閱讀決策」表格中）&lt;/li>
&lt;li>引用 meta-metrics.md 的量測方式（品質驗收場景）&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="要素-4結構一致性">要素 4：結構一致性&lt;/h3>
&lt;p>本 Skill 的所有 reference 遵循相同的段落骨架，便於讀者跨文件快速定位。&lt;/p>
&lt;p>&lt;strong>標準骨架&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="gh"># [Reference 名稱]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="gh">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">[1-3 行情境說明，含適用/不適用聲明]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">&amp;gt; &lt;/span>&lt;span class="ge">**自包含聲明**：[說明閱讀本文件是否需要其他前置文件]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="ge">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="gu">## 何時參閱本文件（或：情境定位）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>[觸發情境表格]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="gu">## 為什麼 [情境] 需要獨立指引
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>[說明此情境的特殊性，與一般文件的差異]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="gu">## [核心原則 × 情境] 的應用
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>（依情境選擇相關原則展開，每個原則含判斷標準 + 反例 + 正確做法）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="gu">## [情境特有章節]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>（例如：Token 節省策略、結構化範例、自檢清單、反模式速查）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">## 延伸閱讀（可選）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>允許調整&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>章節名稱可隨情境調整（「Token 節省策略」僅適用 prompt；「Log 結構化格式」僅適用 logs）&lt;/li>
&lt;li>字數無硬性上限，但每個章節應只承載一個概念（M4 候選閾值 ≤ 3000 tokens）&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="要素-5范例的具體性">要素 5：范例的具體性&lt;/h3>
&lt;p>範例必須足夠具體，讀者能「複製後略作修改直接套用」。&lt;/p>
&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>有反例對照&lt;/td>
 &lt;td>每個原則至少一組「錯 vs 對」範例&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>反例真實&lt;/td>
 &lt;td>反例應來自真實常見錯誤，不是刻意構造的極端案例&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>正例可直接套用&lt;/td>
 &lt;td>正例使用佔位符 &lt;code>{ticket_id}&lt;/code> 等，讀者換掉佔位符即可使用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>說明節省量（如適用）&lt;/td>
 &lt;td>Token 節省策略應標注估計節省比例&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="新增-reference-的決策流程">新增 Reference 的決策流程&lt;/h2>
&lt;p>在建立新 reference 前，先回答以下問題：&lt;/p></description><content:encoded><![CDATA[<p>本文件為 compositional-writing Skill 的 reference 撰寫品質規範。適用對象：新增或修改本 Skill 任何 reference 的作者（人類或代理人）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。品質驗收 metric（M1-M2）見 <code>meta-metrics.md</code>；本文件聚焦「如何寫出符合品質的 reference」。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>觸發情境</th>
          <th>動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>新增一份 reference</td>
          <td>遵循本文件的結構模板和品質標準</td>
      </tr>
      <tr>
          <td>修改現有 reference</td>
          <td>確認改後仍符合五項必要要素</td>
      </tr>
      <tr>
          <td>審查 Skill PR/Ticket</td>
          <td>逐項對照本文件的「驗收清單」</td>
      </tr>
      <tr>
          <td>懷疑某 reference 違反原子化</td>
          <td>執行「職責單一性測試」</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="一份合格-reference-的五項必要要素">一份合格 Reference 的五項必要要素</h2>
<h3 id="要素-1情境聲明何時讀這份文件">要素 1：情境聲明（何時讀這份文件）</h3>
<p>每份 reference 的開頭必須有明確的「情境說明」段落，讓讀者在 30 秒內判斷適用性。</p>
<p><strong>必須回答的問題</strong>：</p>
<ul>
<li>這份 reference 服務哪種寫作情境？</li>
<li>讀者帶著什麼問題進來？</li>
<li>不適用的情境是什麼？</li>
</ul>
<p><strong>範本</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">本 reference 為「撰寫 prompt / instruction / Agent 派發」情境。
</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">適用：給 AI 模型的指令；Ticket Context Bundle；結構化派發 prompt。
</span></span><span class="line"><span class="ln">4</span><span class="cl">不適用：文件（見 writing-documents.md）；程式碼註解（見 writing-code-comments.md）。</span></span></code></pre></div><p><strong>反例</strong>（情境不明確）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">本文件說明寫作原則的應用。</span></span></code></pre></div><hr>
<h3 id="要素-2職責單一性原子化測試">要素 2：職責單一性（原子化測試）</h3>
<p>每份 reference 只服務一個讀者群體、回答一類問題。</p>
<p><strong>判斷方法（原子化測試）</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>必須拆分</td>
      </tr>
  </tbody>
</table>
<p><strong>反例</strong>（雙重職責）：</p>
<p>一份 reference 同時說明「如何撰寫 prompt」AND「如何撰寫其他 reference」——兩個讀者群體、兩種產出，必須拆分。</p>
<hr>
<h3 id="要素-3自包含性m2-通過">要素 3：自包含性（M2 通過）</h3>
<p>讀者<strong>不看 SKILL.md 和其他 reference</strong> 的情況下，能獨立理解並套用。</p>
<p><strong>自包含清單</strong>：</p>
<ul>
<li><input disabled="" type="checkbox"> 開頭有情境說明（不依賴 SKILL.md 判斷適用性）</li>
<li><input disabled="" type="checkbox"> 文件內所有原則在本文件內展開（不引用其他 reference）</li>
<li><input disabled="" type="checkbox"> 專有名詞在首次出現時給出就地定義</li>
<li><input disabled="" type="checkbox"> 無「見 writing-xxx.md」的交叉引用（禁止鏈式）</li>
</ul>
<p><strong>可接受的外部引用</strong>：</p>
<ul>
<li>引用外部公認資源（Anthropic 官方文件、業界標準）</li>
<li>引用 SKILL.md 觸發路由（只在「閱讀決策」表格中）</li>
<li>引用 meta-metrics.md 的量測方式（品質驗收場景）</li>
</ul>
<hr>
<h3 id="要素-4結構一致性">要素 4：結構一致性</h3>
<p>本 Skill 的所有 reference 遵循相同的段落骨架，便於讀者跨文件快速定位。</p>
<p><strong>標準骨架</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># [Reference 名稱]
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">[1-3 行情境說明，含適用/不適用聲明]
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">&gt; </span><span class="ge">**自包含聲明**：[說明閱讀本文件是否需要其他前置文件]
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="ge"></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></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">## 何時參閱本文件（或：情境定位）
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span>[觸發情境表格]
</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"><span class="gu">## 為什麼 [情境] 需要獨立指引
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span>[說明此情境的特殊性，與一般文件的差異]
</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 class="gu">## [核心原則 × 情境] 的應用
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu"></span>（依情境選擇相關原則展開，每個原則含判斷標準 + 反例 + 正確做法）
</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 class="gu">## [情境特有章節]
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu"></span>（例如：Token 節省策略、結構化範例、自檢清單、反模式速查）
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">## 延伸閱讀（可選）</span></span></code></pre></div><p><strong>允許調整</strong>：</p>
<ul>
<li>章節名稱可隨情境調整（「Token 節省策略」僅適用 prompt；「Log 結構化格式」僅適用 logs）</li>
<li>字數無硬性上限，但每個章節應只承載一個概念（M4 候選閾值 ≤ 3000 tokens）</li>
</ul>
<hr>
<h3 id="要素-5范例的具體性">要素 5：范例的具體性</h3>
<p>範例必須足夠具體，讀者能「複製後略作修改直接套用」。</p>
<p><strong>範例品質標準</strong>：</p>
<table>
  <thead>
      <tr>
          <th>標準</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>有反例對照</td>
          <td>每個原則至少一組「錯 vs 對」範例</td>
      </tr>
      <tr>
          <td>反例真實</td>
          <td>反例應來自真實常見錯誤，不是刻意構造的極端案例</td>
      </tr>
      <tr>
          <td>正例可直接套用</td>
          <td>正例使用佔位符 <code>{ticket_id}</code> 等，讀者換掉佔位符即可使用</td>
      </tr>
      <tr>
          <td>說明節省量（如適用）</td>
          <td>Token 節省策略應標注估計節省比例</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="新增-reference-的決策流程">新增 Reference 的決策流程</h2>
<p>在建立新 reference 前，先回答以下問題：</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">1. 此情境是否已有 reference 覆蓋？
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   是 → 擴充現有 reference（不新增）
</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">2. 此情境的讀者群體是否唯一（原子化測試）？
</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">   是 → 繼續
</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">3. 此情境是否需要對核心原則的「情境翻譯」？
</span></span><span class="line"><span class="ln">10</span><span class="cl">   否 → 考慮是否應放入 SKILL.md 核心原則速查表（不需要獨立 reference）
</span></span><span class="line"><span class="ln">11</span><span class="cl">   是 → 建立新 reference
</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">4. 新 reference 加入後，SKILL.md 路由表的每個情境是否仍然唯一對應？
</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></code></pre></div><hr>
<h2 id="驗收清單">驗收清單</h2>
<p>新增或修改 reference 後，逐項確認：</p>
<h3 id="職責單一性">職責單一性</h3>
<ul>
<li><input disabled="" type="checkbox"> 原子化測試通過（一個讀者群體、一類問題）</li>
<li><input disabled="" type="checkbox"> 無在標題或聲明中宣稱承擔多重職責</li>
</ul>
<h3 id="自包含性對應-m2">自包含性（對應 M2）</h3>
<ul>
<li><input disabled="" type="checkbox"> 情境說明段落存在，30 秒內可判斷適用性</li>
<li><input disabled="" type="checkbox"> 核心原則的必要內容在本文件內就地展開</li>
<li><input disabled="" type="checkbox"> 無交叉引用其他 reference（只允許外部公認資源引用）</li>
</ul>
<h3 id="結構一致性">結構一致性</h3>
<ul>
<li><input disabled="" type="checkbox"> 遵循標準骨架（情境說明 → 為什麼獨立 → 原則應用 → 情境章節 → 自檢清單）</li>
<li><input disabled="" type="checkbox"> 所有原則有反例 + 正例對照</li>
</ul>
<h3 id="路由一致性對應-m1">路由一致性（對應 M1）</h3>
<ul>
<li><input disabled="" type="checkbox"> SKILL.md 觸發路由表已更新</li>
<li><input disabled="" type="checkbox"> Directory Index 已更新</li>
<li><input disabled="" type="checkbox"> 新情境與現有情境無重疊</li>
</ul>
<h3 id="范例品質">范例品質</h3>
<ul>
<li><input disabled="" type="checkbox"> 每個原則有至少一組「錯 vs 對」</li>
<li><input disabled="" type="checkbox"> 正例使用 snake_case 佔位符，可直接套用</li>
</ul>
<hr>
<h2 id="與-meta-metricsmd-的分工">與 meta-metrics.md 的分工</h2>
<table>
  <thead>
      <tr>
          <th>文件</th>
          <th>職責</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>本文件（reference-authoring-standards.md）</td>
          <td>如何寫出合格 reference（撰寫指南）</td>
      </tr>
      <tr>
          <td><code>meta-metrics.md</code></td>
          <td>如何量測寫作品質（M1-M5 量化驗收）</td>
      </tr>
  </tbody>
</table>
<p>兩者互補：先用本文件的標準寫，再用 meta-metrics.md 的方法量測。</p>
<hr>
<p><strong>Last Updated</strong>: 2026-04-18
<strong>Version</strong>: 1.0.0 — 從 writing-prompts.md 雙職責拆分：Skill 品質規範獨立為本檔，writing-prompts.md 保留 prompt 寫作情境</p>
]]></content:encoded></item><item><title>Skills — Claude skill 的文章版本</title><link>https://tarrragon.github.io/blog/skills/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/</guid><description>&lt;h2 id="這個資料夾是什麼">這個資料夾是什麼&lt;/h2>
&lt;p>&lt;code>content/skills/&lt;/code> 收錄&lt;strong>從 &lt;code>.claude/skills/&lt;/code> 轉成文章&lt;/strong>的 skill 版本。&lt;/p>
&lt;p>同一份 skill 有兩種存在形式，各自負責不同角色：&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;code>.claude/skills/&amp;lt;name&amp;gt;/&lt;/code>&lt;/td>
 &lt;td>實際 skill，Claude runtime 呼叫&lt;/td>
 &lt;td>Claude&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>content/skills/&amp;lt;name&amp;gt;/&lt;/code>（本處）&lt;/td>
 &lt;td>文章版本，Hugo 渲染成 blog 頁&lt;/td>
 &lt;td>人類讀者&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>兩處內容相同、結構略有差異：&lt;code>.claude/&lt;/code> 版本保留原始 &lt;code>SKILL.md + references/&lt;/code> 巢狀結構以配合 Claude 的路徑解析；&lt;code>content/&lt;/code> 版本扁平化（references 內容升級到同層），以契合 blog 的單層文章呈現。&lt;/p>
&lt;h2 id="目前收錄的-skill">目前收錄的 skill&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Skill&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>入口&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>compositional-writing&lt;/td>
 &lt;td>Zettelkasten 式組合寫作方法論&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/compositional-writing/" data-link-title="Compositional Writing — 組合式寫作方法論" data-link-desc="以 Zettelkasten 為核心、針對程式碼註解、文件、log、prompt、欄位、長篇技術文章、外部分析材料轉教學文章、跨多篇 collection 結構的寫作方法論。核心原則 &amp;#43; 觸發路由 &amp;#43; 情境 reference。">/skills/compositional-writing/&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>requirement-protocol&lt;/td>
 &lt;td>需求確認到實作的對話協議（模糊指令、失敗轉折、漸進驗證等）&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/requirement-protocol/" data-link-title="Requirement Protocol — 需求確認到實作的對話協議" data-link-desc="從需求確認到實作的對話協議：模糊指令澄清、可決定 vs 該確認、失敗 2 次轉折、覆寫成本告知、revert checkpoint、漸進驗證、工具切換時機。六大原則 &amp;#43; 五份情境 reference。">/skills/requirement-protocol/&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>frontend-with-playwright&lt;/td>
 &lt;td>框架無關的前端開發 + Playwright 驗證 + Filter × Source 跨領域 stream 操作（DOM / CSS / JS / framework 共處 / a11y / 資料流）&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/" data-link-title="Frontend with Playwright — 框架無關的前端開發 &amp;#43; Playwright 驗證" data-link-desc="框架無關的前端開發協議 &amp;#43; Playwright 驗證：DOM topology 先於 CSS、CSS / JS 邊界辨識、Playwright 三個位置（假設 / 行為 / 互動驗證）、framework 共處、Reactive 效能、A11y、Filter × Source 跨領域 stream 操作。六大原則 &amp;#43; 七份情境 reference。">/skills/frontend-with-playwright/&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>wrap-decision&lt;/td>
 &lt;td>WRAP 決策框架（錨點確認、資料充足度、擴增選項、實境檢驗、機會成本、行前預想與絆腳索）&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/wrap-decision/" data-link-title="WRAP 決策框架 — 認知偏誤防護與決策品質" data-link-desc="WRAP 決策框架的 blog 好讀版：用錨點確認、資料充足度、選項擴增、現實檢驗、機會成本、行前預想與絆腳索防止自動駕駛式決策。">/skills/wrap-decision/&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="把新-skill-轉成文章的標準流程">把新 skill 轉成文章的標準流程&lt;/h2>
&lt;p>以下步驟假設新 skill 已經放在 &lt;code>.claude/skills/&amp;lt;name&amp;gt;/&lt;/code>，包含 &lt;code>SKILL.md&lt;/code> 和（可能的）&lt;code>references/&lt;/code> 子資料夾。&lt;/p>
&lt;h3 id="step-1複製一份到-contentskills">Step 1：複製一份到 content/skills/&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">cp -R .claude/skills/&amp;lt;name&amp;gt; content/skills/&amp;lt;name&amp;gt;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Claude 版保持原樣、不再動它；所有後續修改都只改文章版。&lt;/p>
&lt;h3 id="step-2扁平化-references">Step 2：扁平化 references/&lt;/h3>
&lt;p>Blog 的文章層級為&lt;strong>單層&lt;/strong>，不保留 references 子資料夾。把所有 reference 移到跟 SKILL.md 同一層：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> content/skills/&amp;lt;name&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">git mv references/*.md .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">git rm references/.gitkeep &lt;span class="c1"># 如果有&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">rmdir references&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="step-3把-skillmd-改成小寫-skillmd">Step 3：把 &lt;code>SKILL.md&lt;/code> 改成小寫 &lt;code>skill.md&lt;/code>&lt;/h3>
&lt;p>Hugo pretty URL 會把檔名小寫輸出（&lt;code>SKILL.md&lt;/code> → &lt;code>/skills/&amp;lt;name&amp;gt;/skill/&lt;/code>），但 &lt;code>mdtools cards&lt;/code> 連結檢查以&lt;strong>檔名大小寫敏感&lt;/strong>的方式解析 URL 回檔案位置。若檔案是 &lt;code>SKILL.md&lt;/code>，cards 嘗試開啟 &lt;code>skill.md&lt;/code> 找不到，就會報 &lt;code>L1-broken-link&lt;/code>。&lt;/p></description><content:encoded><![CDATA[<h2 id="這個資料夾是什麼">這個資料夾是什麼</h2>
<p><code>content/skills/</code> 收錄<strong>從 <code>.claude/skills/</code> 轉成文章</strong>的 skill 版本。</p>
<p>同一份 skill 有兩種存在形式，各自負責不同角色：</p>
<table>
  <thead>
      <tr>
          <th>位置</th>
          <th>角色</th>
          <th>讀者</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>.claude/skills/&lt;name&gt;/</code></td>
          <td>實際 skill，Claude runtime 呼叫</td>
          <td>Claude</td>
      </tr>
      <tr>
          <td><code>content/skills/&lt;name&gt;/</code>（本處）</td>
          <td>文章版本，Hugo 渲染成 blog 頁</td>
          <td>人類讀者</td>
      </tr>
  </tbody>
</table>
<p>兩處內容相同、結構略有差異：<code>.claude/</code> 版本保留原始 <code>SKILL.md + references/</code> 巢狀結構以配合 Claude 的路徑解析；<code>content/</code> 版本扁平化（references 內容升級到同層），以契合 blog 的單層文章呈現。</p>
<h2 id="目前收錄的-skill">目前收錄的 skill</h2>
<table>
  <thead>
      <tr>
          <th>Skill</th>
          <th>主題</th>
          <th>入口</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>compositional-writing</td>
          <td>Zettelkasten 式組合寫作方法論</td>
          <td><a href="/blog/skills/compositional-writing/" data-link-title="Compositional Writing — 組合式寫作方法論" data-link-desc="以 Zettelkasten 為核心、針對程式碼註解、文件、log、prompt、欄位、長篇技術文章、外部分析材料轉教學文章、跨多篇 collection 結構的寫作方法論。核心原則 &#43; 觸發路由 &#43; 情境 reference。">/skills/compositional-writing/</a></td>
      </tr>
      <tr>
          <td>requirement-protocol</td>
          <td>需求確認到實作的對話協議（模糊指令、失敗轉折、漸進驗證等）</td>
          <td><a href="/blog/skills/requirement-protocol/" data-link-title="Requirement Protocol — 需求確認到實作的對話協議" data-link-desc="從需求確認到實作的對話協議：模糊指令澄清、可決定 vs 該確認、失敗 2 次轉折、覆寫成本告知、revert checkpoint、漸進驗證、工具切換時機。六大原則 &#43; 五份情境 reference。">/skills/requirement-protocol/</a></td>
      </tr>
      <tr>
          <td>frontend-with-playwright</td>
          <td>框架無關的前端開發 + Playwright 驗證 + Filter × Source 跨領域 stream 操作（DOM / CSS / JS / framework 共處 / a11y / 資料流）</td>
          <td><a href="/blog/skills/frontend-with-playwright/" data-link-title="Frontend with Playwright — 框架無關的前端開發 &#43; Playwright 驗證" data-link-desc="框架無關的前端開發協議 &#43; Playwright 驗證：DOM topology 先於 CSS、CSS / JS 邊界辨識、Playwright 三個位置（假設 / 行為 / 互動驗證）、framework 共處、Reactive 效能、A11y、Filter × Source 跨領域 stream 操作。六大原則 &#43; 七份情境 reference。">/skills/frontend-with-playwright/</a></td>
      </tr>
      <tr>
          <td>wrap-decision</td>
          <td>WRAP 決策框架（錨點確認、資料充足度、擴增選項、實境檢驗、機會成本、行前預想與絆腳索）</td>
          <td><a href="/blog/skills/wrap-decision/" data-link-title="WRAP 決策框架 — 認知偏誤防護與決策品質" data-link-desc="WRAP 決策框架的 blog 好讀版：用錨點確認、資料充足度、選項擴增、現實檢驗、機會成本、行前預想與絆腳索防止自動駕駛式決策。">/skills/wrap-decision/</a></td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="把新-skill-轉成文章的標準流程">把新 skill 轉成文章的標準流程</h2>
<p>以下步驟假設新 skill 已經放在 <code>.claude/skills/&lt;name&gt;/</code>，包含 <code>SKILL.md</code> 和（可能的）<code>references/</code> 子資料夾。</p>
<h3 id="step-1複製一份到-contentskills">Step 1：複製一份到 content/skills/</h3>





<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">cp -R .claude/skills/&lt;name&gt; content/skills/&lt;name&gt;</span></span></code></pre></div><p>Claude 版保持原樣、不再動它；所有後續修改都只改文章版。</p>
<h3 id="step-2扁平化-references">Step 2：扁平化 references/</h3>
<p>Blog 的文章層級為<strong>單層</strong>，不保留 references 子資料夾。把所有 reference 移到跟 SKILL.md 同一層：</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="nb">cd</span> content/skills/&lt;name&gt;
</span></span><span class="line"><span class="ln">2</span><span class="cl">git mv references/*.md .
</span></span><span class="line"><span class="ln">3</span><span class="cl">git rm references/.gitkeep       <span class="c1"># 如果有</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">rmdir references</span></span></code></pre></div><h3 id="step-3把-skillmd-改成小寫-skillmd">Step 3：把 <code>SKILL.md</code> 改成小寫 <code>skill.md</code></h3>
<p>Hugo pretty URL 會把檔名小寫輸出（<code>SKILL.md</code> → <code>/skills/&lt;name&gt;/skill/</code>），但 <code>mdtools cards</code> 連結檢查以<strong>檔名大小寫敏感</strong>的方式解析 URL 回檔案位置。若檔案是 <code>SKILL.md</code>，cards 嘗試開啟 <code>skill.md</code> 找不到，就會報 <code>L1-broken-link</code>。</p>
<p>避免這個陷阱，把 content 版本的檔名直接改成小寫：</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 mv content/skills/&lt;name&gt;/SKILL.md content/skills/&lt;name&gt;/skill.md</span></span></code></pre></div><p><code>.claude/</code> 版保留原本的 <code>SKILL.md</code>（符合 Claude skill 慣例）。兩邊檔名不同是刻意的：runtime 讀 <code>.claude/SKILL.md</code>、Hugo 渲染 <code>content/skill.md</code>、cards check 能解析。</p>
<h3 id="step-4修改-skillmd-的內部連結">Step 4：修改 skill.md 的內部連結</h3>
<p>skill.md 裡原本的 <code>references/X.md</code> 引用要改。有兩種合法寫法擇一：</p>
<ul>
<li><strong>Markdown 相對路徑</strong>：<code>./X.md</code> — 最無痛，Hugo render hook 會自動解析到對應頁面</li>
<li><strong>Hugo content-root 絕對路徑</strong>：<code>/skills/&lt;name&gt;/&lt;slug&gt;/</code> — 最穩，跟 blog 其他文章遷移後的寫法一致</li>
</ul>
<p>批次替換範例（將 <code>references/</code> 前綴整串刪除）：</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">sed -i <span class="s1">&#39;&#39;</span> <span class="s1">&#39;s|references/|./|g&#39;</span> content/skills/&lt;name&gt;/skill.md</span></span></code></pre></div><p>記得同時更新 <code>## Directory Index</code> 區塊那張 ASCII tree — 刪掉 <code>└── references/</code> 那一層、所有 reference 檔案縮排提到 skill.md 同層。</p>
<h3 id="step-5建立-_indexmdsection-索引">Step 5：建立 <code>_index.md</code>（section 索引）</h3>
<p><code>content/skills/&lt;name&gt;/_index.md</code> 是 Hugo section 的 landing page，URL 是 <code>/skills/&lt;name&gt;/</code>。</p>
<p>必備欄位（Hugo + mdtools 要求）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&lt;Skill 名稱中英對照&gt;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="nt">date</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;YYYY-MM-DD&gt;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&lt;一句話描述 skill 做什麼&gt;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="nt">tags</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">...]</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w"></span><span class="nn">---</span></span></span></code></pre></div><p>內容建議包含：</p>
<ol>
<li><strong>簡述</strong>：這個 skill 是什麼、解決什麼問題</li>
<li><strong>閱讀順序</strong>：場景 1（第一次接觸）與場景 2（已熟悉、直接解決任務）兩個切入點</li>
<li><strong>觸發路由表</strong>：複製 SKILL.md 的「When to Consult This Skill」表，但把檔案路徑替換成 Hugo 絕對 URL（<code>/skills/&lt;name&gt;/&lt;slug&gt;/</code>）</li>
<li><strong>與 blog 其他資料的關係</strong>：對照表（<code>.claude/skills/&lt;name&gt;/</code>、相關 <code>content/posts/...</code>）</li>
<li><strong>Last Updated</strong>：同步日期與 <code>.claude/</code> 版 SKILL.md 的 version</li>
</ol>
<p>可參考 <a href="https://github.com/tarrragon/blog/blob/main/content/skills/compositional-writing/_index.md">compositional-writing 的 <code>_index.md</code></a> 當範本。</p>
<h3 id="step-6補-hugo-frontmatter-到-skillmd-與每份-reference">Step 6：補 Hugo frontmatter 到 skill.md 與每份 reference</h3>
<p>原始 skill.md 與 reference 的 frontmatter 是為 Claude runtime 設計的（欄位多為 <code>name</code>/<code>license</code>/<code>metadata</code>），Hugo 與 mdtools 需要的是 <code>title</code> + <code>date</code>。每份檔案：</p>
<ol>
<li>把原 frontmatter 保留或替換成 Hugo 規格（<code>title</code>、<code>date</code>、<code>description</code>、<code>tags</code>）</li>
<li>刪掉 body 開頭的 <code># H1</code>（Hugo 會從 <code>title</code> 自動生成 H1，保留會觸發 <code>MD025-no-body-h1</code>）</li>
</ol>
<h3 id="step-7跑-make-check-與-hugo-驗收">Step 7：跑 <code>make check</code> 與 <code>hugo</code> 驗收</h3>





<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">make check    <span class="c1"># fmt + lint + cards，三個閘門全綠</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">hugo          <span class="c1"># 確認無 WARN/ERROR，page count 多了對應檔數</span></span></span></code></pre></div><p>幾個常見訊號與對應的修補點：</p>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>原因</th>
          <th>回頭修</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>lint <code>front-matter-required</code> / <code>MD025</code></td>
          <td>有檔案漏掉 frontmatter 或 body H1 沒刪</td>
          <td>Step 6</td>
      </tr>
      <tr>
          <td>cards <code>L1-broken-link target not found</code> 且路徑是 <code>skill/</code></td>
          <td>SKILL.md 沒改成 skill.md（檔名大小寫）</td>
          <td>Step 3</td>
      </tr>
      <tr>
          <td>hugo <code>REF_NOT_FOUND</code></td>
          <td>連結還指向 <code>references/</code></td>
          <td>Step 4</td>
      </tr>
  </tbody>
</table>
<h3 id="step-8更新本-contentskills_indexmd-的目前收錄表">Step 8：更新本 <code>content/skills/_index.md</code> 的「目前收錄」表</h3>
<p>把新 skill 加入上方的表格，含主題一句話與 Hugo URL。</p>
<h3 id="step-9commit">Step 9：commit</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 .claude/skills/&lt;name&gt; content/skills/&lt;name&gt; content/skills/_index.md
</span></span><span class="line"><span class="ln">2</span><span class="cl">git commit -m <span class="s2">&#34;docs(skills): import &lt;name&gt; skill&#34;</span></span></span></code></pre></div><hr>
<h2 id="設計決策備忘">設計決策備忘</h2>
<h3 id="為什麼-claude-保留-referencescontent-扁平">為什麼 <code>.claude/</code> 保留 references/、<code>content/</code> 扁平？</h3>
<p><code>.claude/</code> 是 Claude runtime 的 SKILL 執行環境，SKILL.md 的路徑解析（<code>references/X.md</code>）是 skill 原生協議的一部分 — 改了會破壞 Claude 讀取行為。</p>
<p><code>content/</code> 是 blog 文章，Hugo 的 pretty URL 傾向單層結構（<code>/skills/name/page/</code> 比 <code>/skills/name/references/page/</code> 乾淨）。扁平化也讓 render hook 的 tooltip 與 mdtools 的 card graph 不必穿一層目錄。</p>
<p>兩種結構並行、各自最佳化、用 step 1 的複製動作維持同步。</p>
<h3 id="為什麼不直接-symlink-contentskills--claudeskills">為什麼不直接 symlink <code>content/skills/</code> → <code>.claude/skills/</code>？</h3>
<p>Symlink 會讓兩邊共用 frontmatter 與路徑規範。<code>.claude/</code> 版的 frontmatter 是 skill protocol（<code>name</code>/<code>license</code>/<code>metadata</code>），與 Hugo 要的（<code>title</code>/<code>date</code>）相衝；body 開頭的 <code># H1</code> 是 Claude 讀者的 context signpost，但在 Hugo 會跟 <code>title</code> 生的 H1 重複。結構上看似省事，語意上兩邊是不同受眾的產出，應該允許各自演化。</p>
<hr>
<h2 id="last-updated">Last Updated</h2>
<p>2026-04-24 — 初版：compositional-writing 轉文章完成，記錄標準流程。</p>
]]></content:encoded></item><item><title>Writing Articles — 完整長篇技術文章撰寫指引</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/writing-articles/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/writing-articles/</guid><description>&lt;p>本文件為「撰寫完整長篇技術文章」情境的指引。適用於 blog post、post-mortem、架構決策文件、技術評估報告、概念深度說明等&lt;strong>需要保留思考過程&lt;/strong>的長篇內容。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：本文件不依賴其他 reference。讀完本文件即可獨立寫出合格技術文章。&lt;/p>
&lt;p>&lt;strong>來源&lt;/strong>：methodology 骨架整合自 &lt;code>tarrragon/blog&lt;/code> 的 &lt;code>tech_writing_structure.md&lt;/code>（2026-04-17）；與 compositional-writing 核心原則的映射由本檔補充。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="與原子化寫作的分工">與原子化寫作的分工&lt;/h2>
&lt;p>compositional-writing 的核心主張是「寫原子卡片」，但完整文章是另一種產物：卡片是最小重用單元，文章是把多張卡片用&lt;strong>思考過程&lt;/strong>組織起來。兩者分工如下：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>原子化寫作（其他 references）&lt;/th>
 &lt;th>完整文章（本 reference）&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>grep / 點對點查詢&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>需要多長就多長（完整性優先）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>失敗模式&lt;/td>
 &lt;td>卡片混雜多概念、無法獨立理解&lt;/td>
 &lt;td>觀察直接跳執行、判讀被省略&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>核心目標的差異&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>原子化：讓讀者「用最小認知負擔找到答案」&lt;/li>
&lt;li>完整文章：讓讀者「能複製思考過程，不只複製結論」。結論一行就能給；思考過程才是文章主體。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="適用範圍">適用範圍&lt;/h2>
&lt;p>本 reference 適用於下列技術文章類型：&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>概念說明&lt;/td>
 &lt;td>技術原理、系統架構、協定規格&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>實作教學&lt;/td>
 &lt;td>操作步驟、範例、API 用法&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>架構決策&lt;/td>
 &lt;td>方案比較、選型紀錄、設計文件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>除錯復盤&lt;/td>
 &lt;td>事故紀錄、疑難排解、post-mortem&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>技術評估&lt;/td>
 &lt;td>工具調研、可行性評估&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="八條核心規則">八條核心規則&lt;/h2>
&lt;p>規則一-七 處理「單篇文章內部怎麼寫」的不同層面（段落結構、句子層級、方案對照）。規則八是 meta-level、跨所有規則。規則九指向 &lt;code>managing-article-collections.md&lt;/code> 處理跨多篇的議題。&lt;/p>
&lt;h3 id="規則一階段分層觀察--判讀--策略--執行">規則一：階段分層（觀察 → 判讀 → 策略 → 執行）&lt;/h3>
&lt;h4 id="規則的商業邏輯">規則的商業邏輯&lt;/h4>
&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>觀察&lt;/td>
 &lt;td>描述現況、需求、事實或訊息&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>判讀&lt;/td>
 &lt;td>說明本質、原理、問題所在&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>策略&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;/p>
&lt;h4 id="做法">做法&lt;/h4>
&lt;p>每個主題段落應包含四階段。不同文章類型中四階段的對應：&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;th>除錯復盤&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>觀察&lt;/td>
 &lt;td>需求或背景&lt;/td>
 &lt;td>使用情境&lt;/td>
 &lt;td>現況限制&lt;/td>
 &lt;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>策略&lt;/td>
 &lt;td>不同用法&lt;/td>
 &lt;td>不同操作路徑&lt;/td>
 &lt;td>可選方案&lt;/td>
 &lt;td>可行解法&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>執行&lt;/td>
 &lt;td>程式碼範例&lt;/td>
 &lt;td>操作步驟&lt;/td>
 &lt;td>選定實作&lt;/td>
 &lt;td>修復動作&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h4 id="反例跳過判讀觀察直接進執行">反例（跳過判讀，觀察直接進執行）&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="gu">## 錯誤
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">flutter_broadcasts_4m：Kotlin 1.8 vs Java 17 mismatch
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="gu">## 解法
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">在 subprojects 加上：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">tasks.withType(KotlinCompile).configureEach { jvmTarget = &amp;#39;17&amp;#39; }&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>問題：讀者能複製貼上，但無法回答「為什麼是 configureEach 不是其他方式」「遇到下一個類似問題怎麼想」。思考過程沒有被保存。&lt;/p>
&lt;h4 id="正例">正例&lt;/h4>
&lt;p>每個節點顯式展開成六個子段落：當下看到 → 判讀 → 可選策略 → 選擇與理由 → 結果 → 事後檢視。讀者看完後即使遇到不同錯誤訊息，也能套用同一個四階段推導。&lt;/p>
&lt;h4 id="反模式避開">反模式（避開）&lt;/h4>
&lt;ul>
&lt;li>從觀察直接跳執行，省略判讀與策略&lt;/li>
&lt;li>判讀留下未解問題就進策略&lt;/li>
&lt;li>文章中出現「選擇」卻只列一個選項&lt;/li>
&lt;/ul>
&lt;h4 id="例外">例外&lt;/h4>
&lt;p>純介紹性段落（例如 API 參數說明）可省略「策略」階段，但&lt;strong>不得省略判讀&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h3 id="規則二商業邏輯先於-case">規則二：商業邏輯先於 CASE&lt;/h3>
&lt;h4 id="規則的商業邏輯-1">規則的商業邏輯&lt;/h4>
&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>商業邏輯&lt;/td>
 &lt;td>系統層的概念（為什麼這件事存在、在體系中代表什麼）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CASE&lt;/td>
 &lt;td>實例層的資料（具體的數值、路徑、屬性、設定）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>CASE 單獨存在沒有意義。「&lt;code>jvmTarget = 17&lt;/code>」這個值需要「為什麼 JVM target 要一致」這個概念當容器才能被理解。&lt;/p>
&lt;p>順序顛倒（先 CASE 後商業邏輯）等於讓讀者先記一組沒有脈絡的資料，再倒推抽象概念。這條認知路徑是反向的，多數讀者在倒推失敗後會放棄，即使專業讀者能勉強跟上也會覺得閱讀成本偏高。&lt;/p>
&lt;h4 id="做法-1">做法&lt;/h4>
&lt;p>每個主題段落包含兩層，&lt;strong>順序不得顛倒&lt;/strong>：&lt;/p>
&lt;p>&lt;strong>商業邏輯層&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>主題涉及的系統層概念&lt;/li>
&lt;li>該概念為什麼存在、解決什麼問題&lt;/li>
&lt;li>該類內容的共通本質&lt;/li>
&lt;li>&lt;strong>此層不討論具體數值、路徑、屬性名&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>CASE 層&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>訊息或規格中的關鍵字對應商業邏輯的哪個位置&lt;/li>
&lt;li>具體數值或內容&lt;/li>
&lt;li>從 CASE 推論的結論&lt;/li>
&lt;/ul>
&lt;h4 id="反例直接給-case預設讀者懂背景">反例（直接給 CASE，預設讀者懂背景）&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="gu">### 判讀
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="k">-&lt;/span> 這個子專案的 Java task 產出 bytecode target = 17
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="k">-&lt;/span> 同一個子專案的 Kotlin task 產出 bytecode target = 1.8
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">- 兩者不一致觸發 Kotlin 2.2 的 strict validation&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>專業人士看了懂，但讀者若不知道「bytecode target」「strict validation」在系統中代表什麼，只能抓到字面字串，無法建立模型。&lt;/p></description><content:encoded><![CDATA[<p>本文件為「撰寫完整長篇技術文章」情境的指引。適用於 blog post、post-mortem、架構決策文件、技術評估報告、概念深度說明等<strong>需要保留思考過程</strong>的長篇內容。</p>
<blockquote>
<p><strong>自包含聲明</strong>：本文件不依賴其他 reference。讀完本文件即可獨立寫出合格技術文章。</p>
<p><strong>來源</strong>：methodology 骨架整合自 <code>tarrragon/blog</code> 的 <code>tech_writing_structure.md</code>（2026-04-17）；與 compositional-writing 核心原則的映射由本檔補充。</p></blockquote>
<hr>
<h2 id="與原子化寫作的分工">與原子化寫作的分工</h2>
<p>compositional-writing 的核心主張是「寫原子卡片」，但完整文章是另一種產物：卡片是最小重用單元，文章是把多張卡片用<strong>思考過程</strong>組織起來。兩者分工如下：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>原子化寫作（其他 references）</th>
          <th>完整文章（本 reference）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>產物形態</td>
          <td>可單獨檢索的卡片、欄位、段落</td>
          <td>有首尾、有推導的完整文章</td>
      </tr>
      <tr>
          <td>讀者行為</td>
          <td>grep / 點對點查詢</td>
          <td>從頭讀到尾，追思考過程</td>
      </tr>
      <tr>
          <td>組織原則</td>
          <td>可重用、可跨情境</td>
          <td>可複製思考路徑、不只複製結論</td>
      </tr>
      <tr>
          <td>長度決策</td>
          <td>越短越好（原子性優先）</td>
          <td>需要多長就多長（完整性優先）</td>
      </tr>
      <tr>
          <td>失敗模式</td>
          <td>卡片混雜多概念、無法獨立理解</td>
          <td>觀察直接跳執行、判讀被省略</td>
      </tr>
  </tbody>
</table>
<p><strong>核心目標的差異</strong>：</p>
<ul>
<li>原子化：讓讀者「用最小認知負擔找到答案」</li>
<li>完整文章：讓讀者「能複製思考過程，不只複製結論」。結論一行就能給；思考過程才是文章主體。</li>
</ul>
<hr>
<h2 id="適用範圍">適用範圍</h2>
<p>本 reference 適用於下列技術文章類型：</p>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>典型產物</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>概念說明</td>
          <td>技術原理、系統架構、協定規格</td>
      </tr>
      <tr>
          <td>實作教學</td>
          <td>操作步驟、範例、API 用法</td>
      </tr>
      <tr>
          <td>架構決策</td>
          <td>方案比較、選型紀錄、設計文件</td>
      </tr>
      <tr>
          <td>除錯復盤</td>
          <td>事故紀錄、疑難排解、post-mortem</td>
      </tr>
      <tr>
          <td>技術評估</td>
          <td>工具調研、可行性評估</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="八條核心規則">八條核心規則</h2>
<p>規則一-七 處理「單篇文章內部怎麼寫」的不同層面（段落結構、句子層級、方案對照）。規則八是 meta-level、跨所有規則。規則九指向 <code>managing-article-collections.md</code> 處理跨多篇的議題。</p>
<h3 id="規則一階段分層觀察--判讀--策略--執行">規則一：階段分層（觀察 → 判讀 → 策略 → 執行）</h3>
<h4 id="規則的商業邏輯">規則的商業邏輯</h4>
<p>技術文章的內容從「事實或需求」推導到「動作或結論」，這個過程有四個功能不同的階段。每個階段處理的問題不同、失敗模式不同：</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>處理的問題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>觀察</td>
          <td>描述現況、需求、事實或訊息</td>
      </tr>
      <tr>
          <td>判讀</td>
          <td>說明本質、原理、問題所在</td>
      </tr>
      <tr>
          <td>策略</td>
          <td>列出可選方案並比較</td>
      </tr>
      <tr>
          <td>執行</td>
          <td>具體操作或實作</td>
      </tr>
  </tbody>
</table>
<p>四階段若混著寫，讀者無法區分「這一段失敗是哪個階段」，也無法判斷自己的類似情境卡在哪一步。文章只能被原封不動複製，不能被理解後套用。</p>
<h4 id="做法">做法</h4>
<p>每個主題段落應包含四階段。不同文章類型中四階段的對應：</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>概念說明</th>
          <th>實作教學</th>
          <th>架構決策</th>
          <th>除錯復盤</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>觀察</td>
          <td>需求或背景</td>
          <td>使用情境</td>
          <td>現況限制</td>
          <td>錯誤訊息</td>
      </tr>
      <tr>
          <td>判讀</td>
          <td>概念本質</td>
          <td>工具的位置與功能</td>
          <td>需求本質</td>
          <td>問題根因</td>
      </tr>
      <tr>
          <td>策略</td>
          <td>不同用法</td>
          <td>不同操作路徑</td>
          <td>可選方案</td>
          <td>可行解法</td>
      </tr>
      <tr>
          <td>執行</td>
          <td>程式碼範例</td>
          <td>操作步驟</td>
          <td>選定實作</td>
          <td>修復動作</td>
      </tr>
  </tbody>
</table>
<h4 id="反例跳過判讀觀察直接進執行">反例（跳過判讀，觀察直接進執行）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">## 錯誤
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">flutter_broadcasts_4m：Kotlin 1.8 vs Java 17 mismatch
</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"><span class="gu">## 解法
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">在 subprojects 加上：
</span></span><span class="line"><span class="ln">8</span><span class="cl">tasks.withType(KotlinCompile).configureEach { jvmTarget = &#39;17&#39; }</span></span></code></pre></div><p>問題：讀者能複製貼上，但無法回答「為什麼是 configureEach 不是其他方式」「遇到下一個類似問題怎麼想」。思考過程沒有被保存。</p>
<h4 id="正例">正例</h4>
<p>每個節點顯式展開成六個子段落：當下看到 → 判讀 → 可選策略 → 選擇與理由 → 結果 → 事後檢視。讀者看完後即使遇到不同錯誤訊息，也能套用同一個四階段推導。</p>
<h4 id="反模式避開">反模式（避開）</h4>
<ul>
<li>從觀察直接跳執行，省略判讀與策略</li>
<li>判讀留下未解問題就進策略</li>
<li>文章中出現「選擇」卻只列一個選項</li>
</ul>
<h4 id="例外">例外</h4>
<p>純介紹性段落（例如 API 參數說明）可省略「策略」階段，但<strong>不得省略判讀</strong>。</p>
<hr>
<h3 id="規則二商業邏輯先於-case">規則二：商業邏輯先於 CASE</h3>
<h4 id="規則的商業邏輯-1">規則的商業邏輯</h4>
<p>技術文章的內容包含兩種資訊層次：</p>
<table>
  <thead>
      <tr>
          <th>層次</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>商業邏輯</td>
          <td>系統層的概念（為什麼這件事存在、在體系中代表什麼）</td>
      </tr>
      <tr>
          <td>CASE</td>
          <td>實例層的資料（具體的數值、路徑、屬性、設定）</td>
      </tr>
  </tbody>
</table>
<p>CASE 單獨存在沒有意義。「<code>jvmTarget = 17</code>」這個值需要「為什麼 JVM target 要一致」這個概念當容器才能被理解。</p>
<p>順序顛倒（先 CASE 後商業邏輯）等於讓讀者先記一組沒有脈絡的資料，再倒推抽象概念。這條認知路徑是反向的，多數讀者在倒推失敗後會放棄，即使專業讀者能勉強跟上也會覺得閱讀成本偏高。</p>
<h4 id="做法-1">做法</h4>
<p>每個主題段落包含兩層，<strong>順序不得顛倒</strong>：</p>
<p><strong>商業邏輯層</strong>：</p>
<ul>
<li>主題涉及的系統層概念</li>
<li>該概念為什麼存在、解決什麼問題</li>
<li>該類內容的共通本質</li>
<li><strong>此層不討論具體數值、路徑、屬性名</strong></li>
</ul>
<p><strong>CASE 層</strong>：</p>
<ul>
<li>訊息或規格中的關鍵字對應商業邏輯的哪個位置</li>
<li>具體數值或內容</li>
<li>從 CASE 推論的結論</li>
</ul>
<h4 id="反例直接給-case預設讀者懂背景">反例（直接給 CASE，預設讀者懂背景）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 判讀
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> 這個子專案的 Java task 產出 bytecode target = 17
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> 同一個子專案的 Kotlin task 產出 bytecode target = 1.8
</span></span><span class="line"><span class="ln">5</span><span class="cl">- 兩者不一致觸發 Kotlin 2.2 的 strict validation</span></span></code></pre></div><p>專業人士看了懂，但讀者若不知道「bytecode target」「strict validation」在系統中代表什麼，只能抓到字面字串，無法建立模型。</p>
<h4 id="正例加上商業邏輯層當容器">正例（加上商業邏輯層當容器）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">### 判讀
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**這類錯誤的本質（商業邏輯）**</span>：
</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">Android 每個 module 會分別編譯 Java 跟 Kotlin 原始碼，各自產出
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">JVM bytecode。每個 bytecode 有 target version，決定能在哪些 JVM
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">runtime 上跑。同 module 內若兩種語言產出不同 target，runtime
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">可能觸發 API 相容性問題。Kotlin 2.2 把這個從 warning 提升為 error。
</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="gs">**這次訊息具體說了什麼（CASE）**</span>：
</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"><span class="k">-</span> compileDebugJavaWithJavac (17) → Java 產出 target 17
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> compileDebugKotlin (1.8) → Kotlin 產出 target 1.8
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 符合上面「module 內不一致」的 pattern</span></span></code></pre></div><h4 id="完成標準">完成標準</h4>
<p>段落結束前，讀者應能回答：</p>
<ol>
<li>這個主題在系統中為什麼重要？</li>
<li>這個主題的具體案例對應商業邏輯的哪個位置？</li>
</ol>
<p>若只能回答第二題，<strong>商業邏輯層不足</strong>。</p>
<hr>
<h3 id="規則三評估用內在屬性用品質維度把時間成本留給排程">規則三：評估用內在屬性（用品質維度、把時間成本留給排程）</h3>
<h4 id="規則的商業邏輯-2">規則的商業邏輯</h4>
<p>技術文章經常包含選擇或比較：選 A 不選 B、用 X 不用 Y、選這個架構不選另一個。選擇的優劣取決於方案的<strong>內在屬性</strong>，不取決於執行者的<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>（擴大影響、建立基礎）看起來總是比<strong>消費型方案</strong>（解當前問題）差，但兩者能力的性質不同，不應以同一量度比較。</p>
<p>讀者讀到時間評估會誤以為「這個方案比較好」，但真正該學到的是「兩個方案各自適合什麼情境」。</p>
<h4 id="做法-2">做法</h4>
<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>
      <tr>
          <td>可逆性</td>
          <td>出錯能否回滾</td>
      </tr>
      <tr>
          <td>維護成本</td>
          <td>長期需要多少精力</td>
      </tr>
      <tr>
          <td>可理解性</td>
          <td>未來接手者能否理解</td>
      </tr>
      <tr>
          <td>依賴前提</td>
          <td>建立在什麼假設上，前提變了會如何</td>
      </tr>
  </tbody>
</table>
<h4 id="反例用時間成本換算划不划算">反例（用時間成本換算划不划算）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">這一步多 2 分鐘掃一遍 pub-cache，但可以省下後來約 30 分鐘的
</span></span><span class="line"><span class="ln">2</span><span class="cl">build-炸-修循環。明顯划算。</span></span></code></pre></div><p>讀者學到的是「划不划算」這個執行層結論，沒學到兩個方案的結構性差異。</p>
<h4 id="正例用內在屬性列出方案差異">正例（用內在屬性列出方案差異）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">若當下選擇「A2 + 同步盤點 pub-cache」：
</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 class="k">-</span> 優點：一次盤點所有同類地雷，修復範圍完整；避免之後
</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"><span class="k">-</span> 缺點：盤點結果可能含假陽性；擴大修復範圍可能引入新變數
</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">當下漏掉這個選項的本質問題是：
</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></code></pre></div><h4 id="反模式避開-1">反模式（避開）</h4>
<p>不得以時間成本作為主要評估維度，包含：</p>
<ul>
<li>「這方案比較快」</li>
<li>「多花 X 分鐘省下 Y 分鐘」</li>
<li>「立即可完成」</li>
</ul>
<hr>
<h3 id="規則四事後檢視看判讀品質">規則四：事後檢視看判讀品質</h3>
<h4 id="規則的商業邏輯-3">規則的商業邏輯</h4>
<p>技術文章的品質檢視（review、事故檢討、復盤）常只看「結論對不對」，但多數失敗的根源在<strong>判讀階段</strong>：</p>
<table>
  <thead>
      <tr>
          <th>判讀階段的失敗類型</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>判讀未確認的推論被當結論</td>
      </tr>
      <tr>
          <td>判讀觀察範圍不足（只看單點）</td>
      </tr>
      <tr>
          <td>判讀用類比代替機制驗證</td>
      </tr>
  </tbody>
</table>
<p>這類問題會表現成「結論失敗」的樣子，但改善方向完全不同：</p>
<ul>
<li>結論失敗 → 下次多列幾個選項</li>
<li>判讀失敗 → 下次判讀要更嚴謹、更廣、更實證</li>
</ul>
<p>兩者混為一談會得到「要更仔細」這種無法行動的結論。</p>
<h4 id="做法-3">做法</h4>
<p>文章或決策完成後，<strong>必須回答下列四題</strong>：</p>
<ol>
<li>判讀階段的「需要確認」項目是否全部解答？</li>
<li>觀察範圍是否涵蓋同類情境，或僅處理當前一個？</li>
<li>推論中的類比假設是否驗證？</li>
<li>策略列舉是否完整？</li>
</ol>
<p>任一題答「否」，對應失敗類型必須明確標示：</p>
<table>
  <thead>
      <tr>
          <th>答「否」的題</th>
          <th>失敗類型</th>
          <th>改善方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>題 1</td>
          <td>未確認推論帶入結論</td>
          <td>判讀完成標準要收緊</td>
      </tr>
      <tr>
          <td>題 2</td>
          <td>觀察範圍不足</td>
          <td>擴大搜尋類似情境</td>
      </tr>
      <tr>
          <td>題 3</td>
          <td>類比代替驗證</td>
          <td>機制差異需實證</td>
      </tr>
      <tr>
          <td>題 4</td>
          <td>策略列舉不足</td>
          <td>至少列兩個選項</td>
      </tr>
  </tbody>
</table>
<h4 id="反例只檢視結論歸因模糊">反例（只檢視結論，歸因模糊）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">這次修復走了彎路，下次應該更仔細。</span></span></code></pre></div><p>無法行動。下次遇到類似情境仍會犯同樣錯誤。</p>
<h4 id="正例分類失敗類型對應不同改善方向">正例（分類失敗類型，對應不同改善方向）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><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">| 節點 C  | 需要新資訊（toolchain 時機） | 節點 B 判讀留下「需要確認」但沒補         |
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">| 節點 D1 | 對稱假設                     | 節點 D 判讀用「結構對稱」取代「機制驗證」 |
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">| 節點 F  | 方法呼叫時機                 | 節點 E 判讀沒展開 API 的兩階段行為        |
</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></code></pre></div><p>這樣的檢視產出具體改善方向：「判讀完成標準要收緊」、「機制差異需實證」，而不是模糊的「要更仔細」。</p>
<h4 id="進階避免-hindsight-論述汙染判讀記錄">進階：避免 hindsight 論述汙染判讀記錄</h4>
<p>事後檢視類文章（post-mortem / 設計缺陷分析 / retrospective）有一個常見的失敗模式：論述依賴「結局已發生」的視角、產出的判斷工具只在 case 已出問題後才能用。</p>
<p>例：「[工具] 預設為 [限制版本]、被當 [通用版本] 用了一段時間沒事、後來才爆發問題 → 設計缺陷」——這個論述需要讀者知道結局、且歸因落在個人預見性。換成當下三軸論述「在零成本差異 + 強領域先驗下選了限制更高的選項 → 設計缺陷」、判斷不依賴結局、歸因轉到工具預設與制度。</p>
<p>寫設計檢討時、輪 5「反例 / 邊界」要加掃這個 frame：</p>
<ul>
<li>論述是否需要「後來」「最終」「結果」這類時序詞才站得住？</li>
<li>歸因是否落在「沒預見」「沒考慮」這類個人能力？</li>
<li>結論是否寫成 case-bound 規則（「下次要記得 X」）而不是 portable 工具（「下次跑三軸」）？</li>
</ul>
<p>詳細展開、修法、跨情境多面向應用見 <a href="/blog/report/design-flaw-by-current-axes-not-hindsight/" data-link-title="設計檢討用當下三軸論證、不依賴 hindsight" data-link-desc="本卡提倡用「當下成本對稱條件下選了限制更高的選項」當設計缺陷的判定方式、避免 hindsight 論述把需求演化誤判成設計缺陷。當下三軸論證（成本對稱性 / 可逆性 / 領域先驗）讓判斷不依賴結局發生、且歸因偏向工具預設與制度而非個人預見性。">design-flaw-by-current-axes-not-hindsight</a>。</p>
<hr>
<h3 id="規則五最重要的話優先說資訊優先序">規則五：最重要的話優先說（資訊優先序）</h3>
<h4 id="規則的商業邏輯-4">規則的商業邏輯</h4>
<p>技術說明包含三種資訊：核心原則（是什麼）、示例（長什麼樣）、提醒（不可如何）。AI 產出的技術文章傾向先給觀察或示例，再補「這不是任意的，而是…」的後置定義，導致讀者讀完整句才能理解首句的意義。</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>最後</td>
      </tr>
  </tbody>
</table>
<h4 id="做法-4">做法</h4>
<p>每個技術解釋段落完成後，自問：「如果讀者只讀第一句，他知道這個概念是什麼嗎？」若答案是「不知道，他只看到一個例子」，把核心原則移到第一句。</p>
<h4 id="反例示例先定義後">反例（示例先，定義後）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="sb">`omitempty`</span> 表示欄位為零值時可以不輸出。它不是單純省字，而是宣告
</span></span><span class="line"><span class="ln">2</span><span class="cl">「這個欄位在某些訊息類型中不是必要資料」。</span></span></code></pre></div><p>讀者先記住「為零值時可以不輸出」，才得知這是一個「可選欄位語義標記」——認知路徑反向。</p>
<h4 id="正例定義先行為描述後">正例（定義先，行為描述後）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="sb">`omitempty`</span> 宣告「這個欄位在某些訊息類型中不是必要資料」——它不是
</span></span><span class="line"><span class="ln">2</span><span class="cl">單純省字，而是可選欄位的語義標記；欄位為零值時 JSON 序列化會跳過輸出。</span></span></code></pre></div><h4 id="反模式避開-2">反模式（避開）</h4>
<ul>
<li>「X 是 Y。它不是單純的…，而是…」（觀察在前，定義在後）</li>
<li>「Go 欄位用 A，JSON 欄位用 B。這不是任意轉換…」（示例在前，原則在後）</li>
</ul>
<h4 id="結構推論尾端重點--總結段是資訊優先序失敗的訊號">結構推論：尾端「重點 / 總結」段是資訊優先序失敗的訊號</h4>
<p>資訊優先序講「核心原則放段首」、它的結構對偶是「文章不需要尾端重述」。若一篇文章要靠尾端「重點 / 小結 / 結論 / TL;DR」段重述前文才能讓讀者記住、訊號不是「需要總結」、是正文發散 —— 概念沒在它該出現的位置講清、被攤散、所以尾端要再收一次。把概念補回正文的對應位置（即本規則的 front-load）、尾端重述就失去存在理由。</p>
<p>判準是「<strong>刪掉尾端總結段、看正文站不站得住</strong>」：</p>
<ul>
<li>站得住 → 總結本就冗餘、刪是淨減負擔。</li>
<li>站不住 → 問題在正文組織、該重拆正文段落、不是靠總結救。</li>
</ul>
<p>兩種結果都指向不留尾端重述段。處理段內容時分兩類：純提醒（「養成 X 習慣」「記得回頭確認」）刪 —— 提醒不傳遞新概念、讀者需要時自己回前文；有概念價值的（某設計選擇的理由）併回它在正文該出現的段落、強化 front-load。</p>
<p><strong>位置決定性質、不是「有沒有摘要」</strong>：放文首的摘要（一段 abstract、文件的 description 欄位）服務「讀者決定要不要讀、先建全局框架」、在讀者<strong>還沒讀正文時</strong>提供導航；放文尾的重述在讀者<strong>讀完之後</strong>把已知內容再講一次、無導航功能。同一句「本文講 X 的三個取捨」放文首是合理摘要、放文尾是重述補丁。</p>
<p><strong>例外</strong>：跨章模組 / 長篇的<strong>導覽型結尾</strong>（串連各章、給下一步路由、列模組地圖）傳遞「結構關係」這個正文沒有的新資訊、不是重述、保留。真實內容裡最常見的是「一段重述 + 一句路由」的混合型 —— 修法是外科式：切重述段、留路由句、別整段刪。</p>
<hr>
<h3 id="規則六反例段落用正向陳述建立概念">規則六：反例段落用正向陳述建立概念</h3>
<h4 id="規則的商業邏輯-5">規則的商業邏輯</h4>
<p>「常見錯誤」「反模式」「禁止事項」段落的寫作目標是讓讀者<strong>建立正確概念</strong>，不是讓他記住禁止清單。</p>
<p>讀者在壓力情境下不會回想「規則 4：資料庫不是狀態邊界的替代品」——他只會應用「狀態邊界是程式碼架構的責任」這個概念。負面陳述只告訴讀者什麼是錯的，沒有給讀者正向的概念錨點；沒有錨點的規則記憶壽命短，在新情境中也無法套用。</p>
<h4 id="做法-5">做法</h4>
<p>每個錯誤或反模式段落包含三層，<strong>順序不得顛倒</strong>：</p>
<table>
  <thead>
      <tr>
          <th>層</th>
          <th>內容</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td><strong>正向概念</strong></td>
          <td>這個主題正確的責任劃分是什麼（一句話）</td>
      </tr>
      <tr>
          <td>2</td>
          <td><strong>錯誤的對比</strong></td>
          <td>常見的誤解或替代品，作為概念的反面</td>
      </tr>
      <tr>
          <td>3</td>
          <td><strong>為什麼不足</strong></td>
          <td>誤解為何無法替代正確概念（不只說「不行」）</td>
      </tr>
  </tbody>
</table>
<h4 id="反例只有負面陳述沒有概念錨點">反例（只有負面陳述，沒有概念錨點）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 錯誤四：過早把 memory repository 換成 ORM
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></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">交易語意、copy/DTO 邊界與測試。</span></span></code></pre></div><p>讀者拿到的是「資料庫 ≠ 狀態邊界」這個排除式定義。沒有正向錨點，無法內化，壓力下會遺忘。</p>
<h4 id="正例正向概念先錯誤作對比說明原因">正例（正向概念先，錯誤作對比，說明原因）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 錯誤四：過早把 memory repository 換成 ORM
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">狀態邊界是程式碼架構的責任；資料庫只負責持久化。換成 ORM 只解決
</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">無法代勞的架構責任。
</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">引入資料庫之後，清楚的寫入方法、交易語意、copy/DTO 邊界與測試仍需
</span></span><span class="line"><span class="ln">8</span><span class="cl">顯式設計。</span></span></code></pre></div><p>讀者拿到「狀態邊界 = 程式碼架構責任」這個概念。即使忘了這條規則，概念仍能指引新情境的判斷。</p>
<h4 id="反模式避開-3">反模式（避開）</h4>
<ul>
<li>「X 不是 Y 的替代品」作為段落的唯一說明</li>
<li>「仍然需要 A、B、C」但沒解釋為什麼這些屬於程式碼而非資料庫的責任</li>
<li>錯誤列表只有禁令，沒有對應的正向概念</li>
</ul>
<h4 id="完成標準-1">完成標準</h4>
<p>讀者讀完後能回答：「這個主題正確的責任劃分是什麼？」若只能回答「不能用 X 替代」，<strong>正向概念層不足</strong>。</p>
<hr>
<h3 id="規則七用機會成本語氣多選項並列各標適用情境">規則七：用機會成本語氣（多選項並列、各標適用情境）</h3>
<h4 id="規則的商業邏輯-6">規則的商業邏輯</h4>
<p>技術文章涉及方案選擇、選擇本身有成本與收益。用「正確 / 錯誤」「應該 / 不應該」「正確概念 / 替代方案不足」這種絕對二元語氣描述方案、會誤導讀者：</p>
<table>
  <thead>
      <tr>
          <th>絕對主義（誤導）</th>
          <th>機會成本（準確）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「正確概念是 A」</td>
          <td>「比較好的做法是 A、因為 [情境]」</td>
      </tr>
      <tr>
          <td>「替代方案 B 不足」</td>
          <td>「B 在 [其他情境] 合理、跟 A 的取捨是 [&hellip;]」</td>
      </tr>
      <tr>
          <td>「不應該用 D」</td>
          <td>「D 的成本特別高、只在 [極端情境] 才划算」</td>
      </tr>
      <tr>
          <td>「應該這樣做」</td>
          <td>「預設選 A、依賴前提是 [&hellip;]」</td>
      </tr>
  </tbody>
</table>
<p>絕對主義教讀者「規則」、機會成本教讀者「思考方式」。前者壓力下會忘、後者能套用到新情境。</p>
<p>程式設計極少有絕對正確 — 大部分選擇都是在多目標（覆蓋完整性、風險、可逆性、維護成本、可理解性、依賴前提）之間取捨。把這個事實顯現在語氣上。</p>
<h4 id="做法-6">做法</h4>
<p>設計取捨段落用 A/B/C/D 多選項並列、不用「正確 vs 不足」二元。樣板：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 設計取捨：[維度名]
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></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></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### A：[做法名]（這個專案的預設）
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> **機制**：[做法的核心動作]
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> **選 A 的理由**：[依賴前提 / 維護成本 / 風險容忍度]
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> **適合**：[情境特徵]
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> **代價**：[換到的東西要付的成本]
</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"><span class="gu">### B：[替代做法]
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> **機制**：[...]
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">-</span> **跟 A 的取捨**：[B 換到什麼 / 失去什麼]
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">-</span> **B 比 A 好的情境**：[具體 case]
</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 class="gu">### C：[另一條路]
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">[同上]
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu">### D：[極端做法 / 反模式]
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">-</span> **機制**：[...]
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">-</span> **成本特別高的原因**：[...]
</span></span><span class="line"><span class="ln">26</span><span class="cl">- <span class="gs">**D 才合理的情境**</span>：[極特殊情境、若有；若無則寫「實務上幾乎不存在」]</span></span></code></pre></div><p>關鍵設計原則：</p>
<ol>
<li>預設選項標「（這個專案的預設）」、不是「正確」</li>
<li>每選項都有「適合情境」+「代價」</li>
<li><strong>選項數由議題決定、不強湊到 4 個</strong>（見下節）</li>
<li>多取捨情境分多區塊、不混在一起</li>
</ol>
<h4 id="設計取捨的選項數由議題本身決定">設計取捨的選項數由議題本身決定</h4>
<p>機會成本框架的核心是「呈現議題的真實取捨空間」 — 議題有 N 個合理選項就寫 N 個。A/B/C/D 是這個 corpus 中常見的形態（多數技術議題確實有 3-4 個合理選項）、不該倒推成必須遵守的格式：</p>
<table>
  <thead>
      <tr>
          <th>議題實際選項數</th>
          <th>應該寫幾個</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2 個合理選項 + 1 個反模式</td>
          <td>寫 A / B + D 反模式</td>
      </tr>
      <tr>
          <td>3 個合理選項</td>
          <td>寫 A / B / C</td>
      </tr>
      <tr>
          <td>4 個合理選項</td>
          <td>寫 A / B / C / D</td>
      </tr>
      <tr>
          <td>1 個合理選項（其他都不合理）</td>
          <td>不該用「設計取捨」段落、改用「為什麼選這個」單方論述</td>
      </tr>
  </tbody>
</table>
<p>強湊 4 個的徵兆：</p>
<ul>
<li>反覆出現「實務上幾乎不存在」 — 這是「湊不出 D 的合理情境」的承認、不是有用的描述</li>
<li>「D 才合理的情境」內容很模糊（「極特殊情境」「罕見」） — 表示 D 是強湊的</li>
<li>B / C 的「比 A 好的情境」高度重疊 — 表示 B 與 C 其實是同一選項的兩種表述</li>
</ul>
<h4 id="真反模式直接標明">真反模式直接標明</h4>
<p>當某選項真的是反模式（在所有可預期情境都壞）、不要套「才合理的情境：實務上幾乎不存在」的格式、直接標：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### D：[反模式名]（反模式）
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> **為什麼是反模式**：違反 [X 原則] / 在 [Y 情境] 造成 [Z 後果]
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> **看起來吸引人的原因**：[為什麼有人會選這個 — 通常是「看似省時間」「看似簡單」]
</span></span><span class="line"><span class="ln">5</span><span class="cl">- <span class="gs">**實際發生的代價**</span>：[具體後果]</span></span></code></pre></div><p>這個格式比「假裝有合理情境」更誠實 — 讀者拿到「為什麼不該用」的明確理由、而不是模糊的「幾乎不存在」。</p>
<h4 id="反模式避開-4">反模式（避開）</h4>
<ul>
<li>「正確概念是 X」/「應該用 X」/「不應該用 Y」</li>
<li>把規則六（反例段落）的「正向概念 vs 替代方案不足」直接套到方案對照、變成偽機會成本（仍然絕對主義）</li>
<li>用「最佳實踐」「業界標準」當論證依據（常常是個人偏好的包裝）</li>
<li>強湊 4 個選項導致「實務上幾乎不存在」的低品質 D</li>
<li>抽象層原則 / Pattern 卡片硬寫「設計取捨 A/B/C/D」（這兩類有不同 structure、見規則九）</li>
</ul>
<h4 id="例外-1">例外</h4>
<p>絕對主義語氣只在以下情境合理：</p>
<ul>
<li>安全性（SQL injection、XSS — 是物理層原則、不是工程取捨）</li>
<li>數據完整性（race condition、ACID 違反 — 不該在 production 容忍）</li>
<li>法律 / 合規（GDPR 個資處理、accessibility 法規）</li>
</ul>
<p>這些是「物理 / 法律事實」、不是工程取捨。可以用絕對語氣。</p>
<h4 id="規則七與規則六的關係">規則七與規則六的關係</h4>
<p>規則六處理「反例段落」這個 special case — 當段落只在說「不要做 X」時、要有正向概念當錨點。規則七處理「方案對照段落」這個更廣的情境 — 多選項都有適用範圍、不能用「正確 vs 不足」二元描述。兩規則不衝突：</p>
<ul>
<li>反例 / 常見錯誤段落 → 規則六（正向概念 + 對比錯誤）</li>
<li>多方案對照段落 → 規則七（A/B/C/D 機會成本）</li>
</ul>
<hr>
<h3 id="規則八自我應用-dogfooding--文章在說明某條規則時自己也遵守該規則">規則八：自我應用 (dogfooding) — 文章在說明某條規則時、自己也遵守該規則</h3>
<h4 id="規則的商業邏輯-7">規則的商業邏輯</h4>
<p>教某條規則的文字、本身就是該規則的最自然示範。違反時讀者拿到的訊號是「教學者自己都不信這條規則、那為什麼我要信」 — 比規則內容寫得不好還傷害可信度。</p>
<p>這條規則是 meta-level、跨所有 object-level 規則：</p>
<ul>
<li>教「最重要的話優先說」（規則五）→ 教學文字本身的句首就要寫核心定義</li>
<li>教「反例段落用正向陳述」（規則六）→ 教學文字本身遇到反例也用正向陳述</li>
<li>教「機會成本語氣」（規則七）→ 教學文字本身討論做法選擇時也用 A/B 並列、不用「正確 vs 不足」</li>
<li>教「focus 是議題完整度」→ 教學文字本身的 focus 也要單一</li>
</ul>
<h4 id="做法-7">做法</h4>
<p>寫完每段教學文字後、自問：</p>
<ol>
<li>這段教學的規則 X 適用於這段文字本身嗎？</li>
<li>適用 → 這段文字符合規則 X 嗎？</li>
<li>不符合 → 改寫到符合</li>
</ol>
<p>特別常見的違反：</p>
<table>
  <thead>
      <tr>
          <th>教的規則</th>
          <th>容易違反的句型</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>規則五 最重要的話優先說</td>
          <td>「X 不是 Y、是 Z」（否定先、定義後）</td>
          <td>「X 是 Z；不是 Y」（定義先）</td>
      </tr>
      <tr>
          <td>規則六 反例段落用正向陳述</td>
          <td>教反例段落結構時、自己只列禁止事項</td>
          <td>教學前先給正向概念錨點</td>
      </tr>
      <tr>
          <td>規則七 機會成本語氣</td>
          <td>教 A/B/C/D 時用「不是格式要求、是工具」</td>
          <td>「A/B/C/D 是工具；選項數由議題決定」</td>
      </tr>
      <tr>
          <td>規則八 自我應用</td>
          <td>寫這條規則時自己沒檢查上面幾條</td>
          <td>寫完每段後跑一次 1-3 自問</td>
      </tr>
  </tbody>
</table>
<h4 id="反例">反例</h4>
<p>寫規則七（機會成本語氣）時、用句型「<strong>A/B/C/D 不是格式要求、是工具</strong>」當小節標題。這個句型本身就違反規則五（最重要的話優先說）— 否定先、定義後。</p>
<p>讀者看到的是：「教我用機會成本語氣的人、自己用絕對主義句型」 — 規則的可信度受傷。</p>
<h4 id="正例-1">正例</h4>
<p>同一個小節改寫成「<strong>A/B/C/D 是工具：選項數由議題決定</strong>」 — 定義先、用法明確。讀者看到的是：「教這條規則的文字本身遵守上一條規則」 — 規則網互相補強、可信度上升。</p>
<h4 id="反模式避開-5">反模式（避開）</h4>
<ul>
<li>教「最重要的話優先說」的段落本身、定義被推到後面</li>
<li>教「反例段落用正向陳述」的段落本身、純列禁止事項沒有正向錨點</li>
<li>教「機會成本語氣」的段落本身、用「正確 vs 不足」二元對立描述方案</li>
<li>教某條規則的範例本身違反該規則（看起來像 placeholder 或臨時寫的）</li>
</ul>
<h4 id="完成標準-2">完成標準</h4>
<p>讀者讀完每段教學後、不會察覺「這段文字違反了它教的規則」。每段是該規則的可工作示範。</p>
<hr>
<h3 id="規則九拆分判準與三類文章-structure--由-managing-article-collectionsmd-處理">規則九：拆分判準與三類文章 structure → 由 <code>managing-article-collections.md</code> 處理</h3>
<p>跨多篇 collection 的議題（拆分判準、三類文章 structure 模板、跨篇引用 idiom）由 <code>managing-article-collections.md</code> 統一處理。本 reference 聚焦「單篇文章內部怎麼寫」、不重複展開：</p>
<ul>
<li><strong>拆分判準（focus 是議題完整度）</strong>：見 <a href="/blog/skills/compositional-writing/managing-article-collections/#%e6%8b%86%e5%88%86%e5%88%a4%e6%ba%96focus-%e6%98%af%e8%ad%b0%e9%a1%8c%e5%ae%8c%e6%95%b4%e5%ba%a6" data-link-title="Managing Article Collections — 跨多篇相關文章的結構設計" data-link-desc="compositional-writing reference：跨多篇相關文章的結構設計（三層、素材庫比例、MOC、Pattern 卡片、跨篇引用 idiom）。">managing-article-collections.md → 拆分判準</a></li>
<li><strong>三類文章 structure 模板</strong>：見 <a href="/blog/skills/compositional-writing/managing-article-collections/#%e4%b8%89%e5%b1%a4-structure-%e8%a9%b3%e7%b4%b0%e5%b0%8d%e7%85%a7" data-link-title="Managing Article Collections — 跨多篇相關文章的結構設計" data-link-desc="compositional-writing reference：跨多篇相關文章的結構設計（三層、素材庫比例、MOC、Pattern 卡片、跨篇引用 idiom）。">managing-article-collections.md → 三層 structure 詳細對照</a></li>
</ul>
<p>當寫的是多篇 collection 中的一篇、先讀那邊判斷文章類型（情境檢討 / 抽象層原則 / Pattern 卡片）、再回本 reference 套用對應規則。</p>
<hr>
<h2 id="判讀徵兆對照">判讀徵兆對照</h2>
<p>撰寫過程常見的徵兆與對應的判讀要求。看到徵兆時，作者必須回答對應問題，才能讓判讀階段完成。</p>
<table>
  <thead>
      <tr>
          <th>徵兆</th>
          <th>必答的判讀問題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>單一位置指向（檔案、行號、屬性名）</td>
          <td>為什麼是這個位置出問題？是目標狀態錯了，還是呼叫時機錯了？（現場不一定是根因）</td>
      </tr>
      <tr>
          <td>同類情境第二次出現</td>
          <td>還有哪些同類情境？是否有共通原因？（前次處理範圍不完整）</td>
      </tr>
      <tr>
          <td>修改看似合理但未生效</td>
          <td>修改的生效時機是否晚於覆蓋對象？覆蓋機制是否真的能蓋過目標？</td>
      </tr>
      <tr>
          <td>訊息含「final」「already」「cannot」</td>
          <td>目標何時進入不可修改狀態？修改能否提前到狀態轉換之前？</td>
      </tr>
      <tr>
          <td>訊息含「inconsistent」「mismatch」</td>
          <td>哪一邊是正確的目標值？不一致的方向決定治理施加在哪一邊</td>
      </tr>
  </tbody>
</table>
<p><strong>用法</strong>：把徵兆欄當 grep key，文章寫到該徵兆時展開對應的判讀問題，不得跳過。</p>
<hr>
<h2 id="術語">術語</h2>
<table>
  <thead>
      <tr>
          <th>術語</th>
          <th>定義</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>商業邏輯</td>
          <td>系統層次的概念說明，不涉及具體值</td>
      </tr>
      <tr>
          <td>CASE</td>
          <td>具體內容（數值、路徑、屬性名）</td>
      </tr>
      <tr>
          <td>判讀</td>
          <td>從事實推導本質的過程</td>
      </tr>
      <tr>
          <td>策略</td>
          <td>可選方案</td>
      </tr>
      <tr>
          <td>現場</td>
          <td>訊息直接指向的位置</td>
      </tr>
      <tr>
          <td>根因</td>
          <td>底層原因，不一定等於現場</td>
      </tr>
      <tr>
          <td>投資型策略</td>
          <td>有長期回報的方案（擴大覆蓋、建立認知）</td>
      </tr>
      <tr>
          <td>消費型策略</td>
          <td>只處理當前問題的方案</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="提交自檢清單">提交自檢清單</h2>
<p>提交文章前自檢：</p>
<ul>
<li><input disabled="" type="checkbox"> 四階段（觀察／判讀／策略／執行）完整，或已說明可省略的階段</li>
<li><input disabled="" type="checkbox"> 每個主題段落先商業邏輯後 CASE</li>
<li><input disabled="" type="checkbox"> 技術解釋的資訊優先序正確：核心原則在前，示例居中，提醒在後</li>
<li><input disabled="" type="checkbox"> 無「X 是 Y。它不是單純的…，而是…」這種定義後置句型</li>
<li><input disabled="" type="checkbox"> 尾端無重述型「重點 / 小結 / 結論 / TL;DR」段（刪掉它正文仍站得住=冗餘該刪、站不住=正文要重組；導覽型路由結尾除外）</li>
<li><input disabled="" type="checkbox"> 判讀中所有「需要確認」項目已解答或註明可暫不確認</li>
<li><input disabled="" type="checkbox"> 每個方案比較至少三個評估維度</li>
<li><input disabled="" type="checkbox"> 未以時間成本為主要評估維度</li>
<li><input disabled="" type="checkbox"> 事後檢視四題已回答</li>
<li><input disabled="" type="checkbox"> 失敗類型已標示（若有）</li>
<li><input disabled="" type="checkbox"> 判讀徵兆對照表中出現的徵兆都已展開對應問題</li>
<li><input disabled="" type="checkbox"> 反例／常見錯誤段落有正向概念層（能回答「正確的責任劃分是什麼」）</li>
<li><input disabled="" type="checkbox"> 無純負面陳述段落（「X 不是 Y」沒有對應正向錨點）</li>
<li><input disabled="" type="checkbox"> Title / description / heading / link label / MOC 索引條已跟正文跑同一輪正向陳述、對意圖、grep-ability review</li>
<li><input disabled="" type="checkbox"> 方案對照段落用機會成本語氣（A/B/C/D 多選項並列、不用「正確 vs 不足」二元）</li>
<li><input disabled="" type="checkbox"> 沒有「正確概念是 X」「應該用 X」「不應該用 Y」這類絕對主義語句（除非是安全 / 合規 / 數據完整性等物理 / 法律事實）</li>
<li><input disabled="" type="checkbox"> 文章聚焦的問題能用一句話說完</li>
<li><input disabled="" type="checkbox"> 沒有「+」「與」「以及」綁兩個獨立概念的標題（議題切了一半的訊號）</li>
<li><input disabled="" type="checkbox"> 設計取捨段落的選項數由議題決定（不強湊到 4 個、避免「實務上幾乎不存在」的假反模式）</li>
<li><input disabled="" type="checkbox"> 真反模式直接標「反模式 — 違反 X 原則」、不假裝有合理情境</li>
<li><input disabled="" type="checkbox"> 教某條規則的段落本身遵守該規則（規則八 dogfooding）</li>
<li><input disabled="" type="checkbox"> 文章類型分類正確（情境檢討 / 抽象層原則 / Pattern 卡片、見 <code>managing-article-collections.md</code>）、用對應的 structure</li>
<li><input disabled="" type="checkbox"> 開頭語氣：把「你」換成「我們」後語句仍自然嗎？（彆扭 = 恐嚇式、改分享式）</li>
<li><input disabled="" type="checkbox"> 每段補充：這段消失後讀者的閱讀體驗會變差嗎？（不會 = meta 資訊或主題偏移、刪除）</li>
<li><input disabled="" type="checkbox"> 描述行為的句子：在描述事實、還是在分配責任？（「承認」「暴露」→ 改「信號」「反映」「顯示」）</li>
<li><input disabled="" type="checkbox"> 把 pattern 歸因為 AI 特有的句子：這個 pattern 人類作者也會犯嗎？（會 → 改為通用觀察、AI 降為觸發脈絡；「AI 的發生率更高」需要對照證據、沒有就降為假說或刪除）</li>
</ul>
<hr>
<h2 id="多輪-re-read-passmulti-pass-refinement">多輪 Re-read Pass（multi-pass refinement）</h2>
<p>寫完上方自檢還不是 done — 自檢是「同 frame 的最後一掃」、不是 multi-pass。Multi-pass 要求每輪用<strong>不同 frame</strong> catch 不同層的錯（<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 — 不是所有錯誤都該被攔截。">literal-interception-vs-behavioral-refinement</a> / <a href="/blog/report/writing-multi-pass-review/" data-link-title="Writing 的 multi-pass review：N 輪 review、每輪換 frame" data-link-desc="寫文章 / 註解 / 文件 / prompt 的「寫」不是單次動作 — 是 N 輪 review。第 1 輪生成、第 2 輪對意圖（#67）、第 3 輪檢查機會成本語氣、第 4 輪 grep-ability、第 5 輪反例 / 邊界。每輪不同 frame、單輪寫不出全部維度。本卡是 #82 在「寫」這個 output 動作的具體實例。">writing-multi-pass-review</a>）。</p>
<p>跑下表前先做 surface enumeration：列出 body surface（段落、表格、範例）與 metadata / navigation surface（title、description、tags、heading、link label、MOC / index entry、slug / filename）。每輪 frame 都掃同一份 surface 清單，讓正文與讀者入口維持同一個概念錨點。細節見 <a href="/blog/report/metadata-surface-in-writing-review/" data-link-title="Metadata surface 要納入寫作 review 範圍" data-link-desc="寫作 review 的 surface 包含正文與 metadata surface：title、description、frontmatter、heading、link label、MOC 索引條。正文通過 positive wording 或 multi-pass review 只代表 body surface 收斂；讀者入口與索引入口也要跑同一套 frame，才能讓文章在第一眼、搜尋與跨篇路由上維持同一個概念錨點。">metadata-surface-in-writing-review</a>。</p>
<p>文章用的五輪 + 兩輪文章專屬：</p>
<table>
  <thead>
      <tr>
          <th>輪</th>
          <th>Frame</th>
          <th>文章專用 checklist</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>生成</td>
          <td>idea 從頭寫到尾、不停下改、預期會有粗糙</td>
      </tr>
      <tr>
          <td>2</td>
          <td>對意圖（<a href="/blog/report/ease-of-writing-vs-intent-alignment/" data-link-title="寫作便利度跟意圖對齊反相關" data-link-desc="寫程式時最容易寫出的版本、通常是離意圖最遠的版本。便利度建立在「現有上下文 / 已 materialize 資料 / 已存在 API」上、而意圖對齊需要找到正確的層、處理上游、跨抽象層 — 兩者方向相反。識別這個反相關 = 識別自己掉進「容易寫的陷阱」。">ease-of-writing-vs-intent-alignment</a>）</td>
          <td>開頭一句、title、description、MOC hook 都點同一個核心責任嗎？段落順序由「易讀」決定不是「好寫」決定？去掉視覺標記後還能讀嗎？</td>
      </tr>
      <tr>
          <td>3</td>
          <td>機會成本語氣</td>
          <td>全 surface grep「應該/必須/不行/正確/唯一」、絕對詞翻成 trade-off</td>
      </tr>
      <tr>
          <td>4</td>
          <td>Grep-ability / 命名 / 術語</td>
          <td>title / heading / link label / 段首關鍵字前置、表格欄位用 <code>:</code> <code>|</code> <code>→</code> 友善分隔、slug 對應 title；術語保留原文錨點與完整名詞頭</td>
      </tr>
      <tr>
          <td>5</td>
          <td>反例 / 邊界</td>
          <td>「何時不適用」段寫了嗎？「跟其他卡的關係」表完整嗎？反模式給「為什麼不好」+「修法」嗎？設計檢討類文章另掃 <a href="/blog/report/design-flaw-by-current-axes-not-hindsight/" data-link-title="設計檢討用當下三軸論證、不依賴 hindsight" data-link-desc="本卡提倡用「當下成本對稱條件下選了限制更高的選項」當設計缺陷的判定方式、避免 hindsight 論述把需求演化誤判成設計缺陷。當下三軸論證（成本對稱性 / 可逆性 / 領域先驗）讓判斷不依賴結局發生、且歸因偏向工具預設與制度而非個人預見性。">hindsight 論述</a></td>
      </tr>
      <tr>
          <td>6</td>
          <td>Cross-link 健康度</td>
          <td>引用的卡都還在嗎、被引用該卡是否反向引回（雙向）、新卡有沒有加進 collection index</td>
      </tr>
      <tr>
          <td>7</td>
          <td>索引條 vs 內容</td>
          <td>MOC / index entry 的索引描述、link label、文章 title 與正文第一段是否指向同一個核心責任</td>
      </tr>
      <tr>
          <td>8</td>
          <td>Keyword bank（換工具）</td>
          <td>跑 grep 比對固定 keyword list（口語修辭 / 廢話前綴 / 地區漂移 / 依賴 code / 裝飾符號 / 對讀者喊話 / 自評誇飾 / 必然性框架）、不靠 reviewer 記憶；<strong>命中是候選不是判決</strong>、命中後要語意判定（建立概念的違規 vs 合規的 hook / 反例 / 真必然）——詳見 <a href="/blog/report/colloquial-rhetoric-erodes-technical-precision/" data-link-title="口語化修辭在判斷工具型段落會稀釋技術精度" data-link-desc="技術文章的「判斷工具型段落」（讀者用來判斷自己 case 的論述）用「一輩子」「碰巧能用」「立刻撞牆」「沒事」這類口語修辭、會稀釋精度。修法是把口語修辭翻譯回技術屬性語言。但 hook / 引言 / narrative 段落用口語仍然合理——這是情境化的取捨、不是精度永遠優於可讀性。">colloquial-rhetoric</a> + <a href="/blog/report/regional-terminology-alignment/" data-link-title="地區用語對齊：寫作前先確定讀者的中文語料" data-link-desc="繁中 vs 簡中的用詞差異不只是字形（屏 / 螢幕、視頻 / 影片）、更是技術術語跟業務情境的精度差。寫作前要先確定讀者的中文語料、避免用對方語料中不存在或意思偏移的詞。常見漂移：硬體（屏 / 螢幕）、檔案系統（文件 / 檔案）、概念詞（默認 / 預設）、修辭詞（質量 / 品質）、混雜情境的英文中文比例。">regional-terminology</a> + <a href="/blog/report/prose-self-contained-without-code-reference/" data-link-title="商業邏輯論述要 self-contained：不依賴 code 才能被理解" data-link-desc="技術文章在「不放 code 的段落」仍然要 self-contained——商業邏輯論述不能預設讀者已經看過 code、用「那個 payload 第二段」「剛才的變數」這類 reference 等於把理解門檻轉嫁給讀者去翻 code。修法是把 reference 翻譯成「用名詞 / 角色 / 條件描述」的 self-contained 句子、即使讀者跳過所有 code block 也能理解論述。">prose-self-contained</a> + <code>decorative-symbols</code>（skill 內部支撐卡） + <a href="/blog/report/teaching-register-states-not-addresses-reader/" data-link-title="教材用中性陳述、不對讀者喊話" data-link-desc="教材的 register 是中性陳述概念、不是對讀者說話。三種對讀者喊話的形式 —— 安撫情緒（很多人卡在）、第二人稱代入（你天天寫）、祈使控制閱讀（先讀懂 / 別搞混）—— 表面不同、共同違反是把讀者當成要管理的對話對象、而非把概念講清楚。問題不在精度（「你天天寫的 int count」精度完全正確）、在 stance。修法是換成中性陳述（常見的 int count）或描述性名詞標題（簽章的型別與名字拆解）。邊界：hook / narrative 段落的輕度第二人稱可幫讀者進入、不一律禁。">teaching-prose-neutral-register</a></td>
      </tr>
      <tr>
          <td>9</td>
          <td>Reader simulation（換視角）</td>
          <td>四個 lens：(a) <strong>自包含性</strong>——拿掉所有 code block 重讀論述是否仍能 parse？跳段直接讀能拿到關鍵資訊？(b) <strong>register/stance</strong>——這段在「陳述概念」、還是在「管理 / 評價 / 絕對化 / 恐嚇讀者」（喊話 / 誇飾 / 必然 / 恐嚇）？register 類無穩定關鍵詞、keyword bank（輪 8）抓不到、<strong>reader-sim 是主、keyword bank 是輔</strong>；且這類最依賴 external cold-read、同 reviewer 模擬有限——production 教材建議刻意換視角或外部讀者。(c) <strong>meta 資訊 vs 內容</strong>——這段在描述內容、還是在描述寫作過程（「整理目的」「先交代脈絡，否則…」「本文邊界是…」）？meta 資訊服務作者的組織需求、不是讀者的閱讀需求；判準是「這段消失後、讀者的閱讀體驗會變差嗎？」——不會 → 刪除。AI 生成的文章高頻出現 meta 殘留、因為 AI 的「規劃→組織→寫作」推理過程會外露到文章中。同類問題（AI prompt context 中的系統知識洩漏到面向讀者的論述）也用此 lens catch：每段補充問「回答的是文章標題承諾的問題、還是衍生子問題？」。(d) <strong>AI 歸因過度</strong>——這段把 pattern 歸因為 AI 特有現象、人類作者也會犯嗎？AI 生成內容系統性地把通用寫作 / 工程 pattern 框為「AI 特有」（「AI 的發生率更高」「因為推理和生成在同一序列」），縮窄適用範圍且背上無法證實的舉證負擔。判準：把句中「AI」換成「作者」、論點是否仍然成立？成立 → 改為通用觀察、AI 降為觸發脈絡（「兩個 case 來自 AI 生成的文件、但此 pattern 不限於 AI」）；斷言 AI 發生率更高需要對照證據、沒有就降為假說或刪除。詳見 <a href="/blog/report/teaching-register-states-not-addresses-reader/" data-link-title="教材用中性陳述、不對讀者喊話" data-link-desc="教材的 register 是中性陳述概念、不是對讀者說話。三種對讀者喊話的形式 —— 安撫情緒（很多人卡在）、第二人稱代入（你天天寫）、祈使控制閱讀（先讀懂 / 別搞混）—— 表面不同、共同違反是把讀者當成要管理的對話對象、而非把概念講清楚。問題不在精度（「你天天寫的 int count」精度完全正確）、在 stance。修法是換成中性陳述（常見的 int count）或描述性名詞標題（簽章的型別與名字拆解）。邊界：hook / narrative 段落的輕度第二人稱可幫讀者進入、不一律禁。">teaching-prose-neutral-register</a></td>
      </tr>
      <tr>
          <td>10</td>
          <td>Self-criticism（換層次）</td>
          <td>我跑的 N 輪 catch 哪些問題類型？同個規則下還有哪些違反句型沒掃到？framework 是否有 known blind spot？——詳見 <a href="/blog/report/multi-pass-review-frame-granularity-blindspot/" data-link-title="Multi-pass review 的 frame 顆粒度盲點：抽象規則 → 具體訊號的轉譯不完整" data-link-desc="Multi-pass review 跑了 4 輪、字句層問題（口語修辭 / 地區用語 / 依賴 code / 廢話前綴）仍漏 catch——揭露 frame 顆粒度盲點：抽象規則（如「機會成本語氣」「正向陳述」「最重要的話優先說」）沒被轉譯成具體訊號（如 grep keyword bank：「一輩子 / 碰巧 / 撞牆 / 下次 X 時 / 不是 A 而是 B」）。修法是把每條規則展開成可 grep 的 keyword bank、加 reader simulation 輪、加 self-criticism 輪。">multi-pass-review-frame-granularity</a></td>
      </tr>
  </tbody>
</table>
<p>跳輪規則同 <a href="/blog/report/writing-multi-pass-review/" data-link-title="Writing 的 multi-pass review：N 輪 review、每輪換 frame" data-link-desc="寫文章 / 註解 / 文件 / prompt 的「寫」不是單次動作 — 是 N 輪 review。第 1 輪生成、第 2 輪對意圖（#67）、第 3 輪檢查機會成本語氣、第 4 輪 grep-ability、第 5 輪反例 / 邊界。每輪不同 frame、單輪寫不出全部維度。本卡是 #82 在「寫」這個 output 動作的具體實例。">writing-multi-pass-review</a> — 短文 / 即時 note 跳 4-7、production 卡片 / 教學文章全跑；輪 8-10 是 production 教學文章專用、catch 字句層問題、跑 N 輪後仍漏 catch 同類問題時觸發。</p>
<h3 id="術語檢查中文入口--原文錨點--完整名詞頭">術語檢查：中文入口 + 原文錨點 + 完整名詞頭</h3>
<p>術語檢查屬於輪 4 的命名子場景，production 卡片 / 教學文章要把它當成獨立子 pass。技術文章中的術語同時承擔可讀性、可搜尋性與概念邊界；中文讓 reader 進入段落，原文讓概念可回溯，完整名詞頭讓中文離開原句仍能獨立成立。</p>
<p>翻譯檢查先看句內邏輯。把中文譯名放回原句後，要檢查它跟主詞、動詞、修飾語、因果關係是否成立；如果譯名讓句子多出原文沒有的前提，或讓讀者追問方向改變，這個翻譯就有問題。</p>
<table>
  <thead>
      <tr>
          <th>檢查項</th>
          <th>問題</th>
          <th>修法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>原文錨點</td>
          <td>學術 / 標準 / 方法論術語第一次出現是否保留原文？</td>
          <td>寫成「中文術語（original term）」</td>
      </tr>
      <tr>
          <td>譯名對位</td>
          <td>中文是否帶入原詞沒有的文化 / 政治 / 日常語意？</td>
          <td>回原文語境重選中文，必要時在定義段列常見譯名</td>
      </tr>
      <tr>
          <td>完整名詞頭</td>
          <td>中文壓縮後是否仍回答「這是什麼」？</td>
          <td>補「盲點 / 偏誤 / 風險 / 模式 / 檢查 / 策略」等 head noun</td>
      </tr>
      <tr>
          <td>全文一致</td>
          <td>同一術語是否出現多個中文譯名或多個詞尾？</td>
          <td>選 canonical 譯名與 head noun 後全篇替換</td>
      </tr>
      <tr>
          <td>Metadata / navigation 同步</td>
          <td>title、description、heading、link label 是否同樣對位？</td>
          <td>跟正文第一個定義使用同一組術語</td>
      </tr>
      <tr>
          <td>句內邏輯</td>
          <td>譯名是否讓句子多出原文沒有的前提？</td>
          <td>標出主詞 / 動詞 / 修飾語 / 因果，逐項檢查是否成立</td>
      </tr>
      <tr>
          <td>讀者追問</td>
          <td>reader 看到中文會追問正確問題嗎？</td>
          <td>問「他會問 A 還是 B？」；問錯方向就重譯</td>
      </tr>
  </tbody>
</table>
<p>例：<code>paternalism</code> 在決策 / 倫理脈絡裡較接近「家長主義」；翻成帶 gender 聯想的詞會讓概念漂移。例：中文壓縮詞若只剩「盲」一個字，reader 無法判斷它是盲點、盲區或盲測；補成「多步驟成功率盲點」才有完整分類。</p>
<p>完整翻譯 / 轉譯流程見 <a href="/blog/skills/compositional-writing/translation-review/" data-link-title="Translation Review — 文章翻譯與轉譯檢查" data-link-desc="compositional-writing reference：把英文材料轉成中文、把既有文章改寫成另一種語言、檢查術語誤譯與句內邏輯對位。">translation-review</a>。支撐原則見 <a href="/blog/report/terminology-keeps-original-anchor/" data-link-title="術語翻譯要保留原文錨點" data-link-desc="翻譯術語時、中文名稱負責降低閱讀門檻，原文名稱負責鎖定概念邊界。只留中文會把 reader 帶進中文詞的日常歧義，只留英文會提高閱讀成本；中文後接英文括號是技術文章的穩定折衷。">terminology-keeps-original-anchor</a> 與 <a href="/blog/report/compressed-chinese-terms-need-head-noun/" data-link-title="中文壓縮術語要保留完整名詞頭" data-link-desc="中文技術寫作可以壓縮長詞，但不能省到只剩形容詞或單字修飾。像「多步驟 perplexity 盲」這類詞少了完整名詞頭，讀者無法判斷是在說盲點、盲區、盲測或失明比喻。壓縮後仍要能獨立回答「這是什麼」。">compressed-chinese-terms-need-head-noun</a>。</p>
<h3 id="層次意識frame-是-horizontallayer-是-vertical">層次意識：frame 是 horizontal、layer 是 vertical</h3>
<p>Multi-pass review 的「N 輪不同 frame」是 horizontal 軸（同一份文字、5 個視角輪流看）。常見的誤區是 5 輪都落在同一個 vertical layer（例如全部在看視覺層）、漏掉語意 / 邏輯層 — frame 換了但 layer 沒換、根本問題不會被觸碰。</p>
<p>技術文章中常見的三個檢查層次（每輪 frame 內、都要掃一次三層）：</p>
<table>
  <thead>
      <tr>
          <th>層次</th>
          <th>檢查內容</th>
          <th>修法</th>
          <th>根本問題的訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>邏輯層</strong></td>
          <td>論證是否完整、推導是否跳過、方案是否充分列舉</td>
          <td>重新分概念、補論證</td>
          <td>「有未回答的假設」「觀察直接跳執行」「只列一個選項」</td>
      </tr>
      <tr>
          <td><strong>語意層</strong></td>
          <td>概念是否清晰、有無依賴視覺標記、概念邊界是否明確</td>
          <td>改表達結構、加分段</td>
          <td>「用 emoji 作為唯一區分」「同一行表達兩個不同概念」「去掉顏色就無法理解」</td>
      </tr>
      <tr>
          <td><strong>視覺層</strong></td>
          <td>排版是否對齊、圖表是否正確渲染、容器寬度是否適配</td>
          <td>CSS / 排版 / 渲染</td>
          <td>「emoji 斷行」「圖表無法顯示」「文字超出邊界」</td>
      </tr>
  </tbody>
</table>
<p><strong>關鍵原則</strong>：</p>
<ol>
<li>邏輯和語意錯誤會表現成視覺問題（症狀在淺層、根因在深層）</li>
<li>視覺工具的 ceiling 在語意 / 邏輯層 — 用 CSS / emoji 蓋下游症狀 = false confidence、根因換 context 復發</li>
<li>若多輪都在檢查視覺問題、表示 frame 換了但 layer 沒換、根本問題還沒被觸碰</li>
</ol>
<p><strong>反例（修錯層次）</strong>：</p>
<p>emoji 在容器窄時斷行：</p>
<ul>
<li><strong>第一直覺修視覺</strong>：加 <code>white-space: nowrap</code> → 文字溢出 → 加 <code>overflow: hidden</code> → 症狀堆疊、根因（兩個概念擠在一行）更深埋</li>
<li><strong>追問層次後修結構</strong>：HIGHLIGHT 跟 REVERSE 是兩個獨立概念、不該擠在同一行 — 拆成獨立列表項、所有下游症狀同時消失</li>
</ul>
<p>判讀層次比修法重要 — 純視覺問題（容器寬度、字體大小、跨瀏覽器）用 CSS 是對的；語意 / 邏輯下游症狀用 CSS 是 false confidence。</p>
<hr>
<h2 id="與核心原則的映射">與核心原則的映射</h2>
<p>本規則四條與 compositional-writing 核心原則對應關係：</p>
<table>
  <thead>
      <tr>
          <th>本 reference 規則</th>
          <th>對應核心原則</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>規則一：階段分層</td>
          <td>原子化 + 意圖顯性</td>
          <td>每個階段是自包含的認知單元；階段名即宣告該段功能</td>
      </tr>
      <tr>
          <td>規則二：商業邏輯先於 CASE</td>
          <td>意圖顯性 + 原子化</td>
          <td>商業邏輯段是可跨 CASE 重用的容器；CASE 是該容器的實例填充</td>
      </tr>
      <tr>
          <td>規則三：評估用內在屬性</td>
          <td>欄位設計</td>
          <td>內在屬性是穩定的比較軸；時間消耗是執行者的狀態，不應混入評估欄位</td>
      </tr>
      <tr>
          <td>規則四：事後檢視看判讀品質</td>
          <td>可查詢性 + 欄位設計</td>
          <td>失敗類型是可 grep 的分類鍵；改善方向與失敗類型分欄，避免「要更仔細」這種無欄位化結論。設計檢討類文章另外要避免 hindsight 論述汙染判讀（見 <a href="/blog/report/design-flaw-by-current-axes-not-hindsight/" data-link-title="設計檢討用當下三軸論證、不依賴 hindsight" data-link-desc="本卡提倡用「當下成本對稱條件下選了限制更高的選項」當設計缺陷的判定方式、避免 hindsight 論述把需求演化誤判成設計缺陷。當下三軸論證（成本對稱性 / 可逆性 / 領域先驗）讓判斷不依賴結局發生、且歸因偏向工具預設與制度而非個人預見性。">principles/design-flaw-by-current-axes-not-hindsight</a>）</td>
      </tr>
      <tr>
          <td>規則五：最重要的話優先說</td>
          <td>意圖顯性</td>
          <td>核心原則必須在句首，示例和提醒跟在後面；禁止「觀察→示例→事後定義」的反向認知路徑</td>
      </tr>
      <tr>
          <td>規則六：反例段落用正向陳述</td>
          <td>意圖顯性 + 原子化</td>
          <td>錯誤段落先說正確概念，再用錯誤作對比；禁止只有排除式定義（「X 不是 Y」）而無正向錨點</td>
      </tr>
      <tr>
          <td>規則七：機會成本語氣</td>
          <td>意圖顯性 + 欄位設計</td>
          <td>方案對照用 A/B/C/D 多選項並列、不用「正確 vs 不足」二元；選項數由議題決定不強湊；真反模式直接標明</td>
      </tr>
      <tr>
          <td>規則八：自我應用 (dogfooding)</td>
          <td>意圖顯性（meta）</td>
          <td>教某條規則的段落本身遵守該規則；違反時規則的可信度受傷</td>
      </tr>
      <tr>
          <td>規則九：跨多篇議題 → managing-article-collections.md</td>
          <td>（指引轉介）</td>
          <td>拆分判準（focus）/ 三類文章 structure / 跨篇引用 idiom 由跨篇 reference 處理</td>
      </tr>
  </tbody>
</table>
<p><strong>反向理解</strong>：原子化要求「每張卡一個概念」，完整文章要求「每個階段一個功能」；兩者是同一個認知負擔原則在不同粒度上的體現。</p>
<hr>
<p><strong>Last Updated</strong>: 2026-06-25
<strong>Version</strong>: 0.9.0 — 從工具 opinion 文章 review 回流：(1) 輪 9 reader-sim 加第三 lens「meta 資訊 vs 內容」— 涵蓋 meta-commentary 殘留（AI 推理過程外露）+ 主題偏移（prompt context 系統知識洩漏）兩個 design gap；(2) 提交自檢清單加 3 個生成端自問句（恐嚇式 hook「你→我們」測試 / meta 刪除測試 / 歸因語氣判斷）。生成端自問句成本低於審查端 grep 擴充、且 4 個 design gap 中 meta-commentary 和 topic-drift 沒有穩定通用關鍵詞不適合 grep。</p>
<p><strong>Last Updated</strong>: 2026-05-06
<strong>Version</strong>: 0.8.1 — WRAP 審視後降 over-claim：5 張對應 report 卡（#110-114）都加「論述基礎與限制」段揭露樣本量限制；補上 <a href="/blog/report/regional-terminology-alignment/" data-link-title="地區用語對齊：寫作前先確定讀者的中文語料" data-link-desc="繁中 vs 簡中的用詞差異不只是字形（屏 / 螢幕、視頻 / 影片）、更是技術術語跟業務情境的精度差。寫作前要先確定讀者的中文語料、避免用對方語料中不存在或意思偏移的詞。常見漂移：硬體（屏 / 螢幕）、檔案系統（文件 / 檔案）、概念詞（默認 / 預設）、修辭詞（質量 / 品質）、混雜情境的英文中文比例。"><code>regional-terminology-alignment</code></a> portable principle（地區用語對齊、輪 8 keyword bank 引用）；同步 <a href="/blog/report/design-flaw-by-current-axes-not-hindsight/" data-link-title="設計檢討用當下三軸論證、不依賴 hindsight" data-link-desc="本卡提倡用「當下成本對稱條件下選了限制更高的選項」當設計缺陷的判定方式、避免 hindsight 論述把需求演化誤判成設計缺陷。當下三軸論證（成本對稱性 / 可逆性 / 領域先驗）讓判斷不依賴結局發生、且歸因偏向工具預設與制度而非個人預見性。"><code>design-flaw-by-current-axes-not-hindsight</code></a> 的 WRAP 修補（個人 vs 制度歸因軟化、多面向標假設性對比）
<strong>Version</strong>: 0.8.0 — Multi-pass review 加輪 8-10（keyword bank / reader simulation / self-criticism）、處理「跑了 N 輪、字句層問題仍漏」的盲點；新增三張內部 principle：<a href="/blog/report/colloquial-rhetoric-erodes-technical-precision/" data-link-title="口語化修辭在判斷工具型段落會稀釋技術精度" data-link-desc="技術文章的「判斷工具型段落」（讀者用來判斷自己 case 的論述）用「一輩子」「碰巧能用」「立刻撞牆」「沒事」這類口語修辭、會稀釋精度。修法是把口語修辭翻譯回技術屬性語言。但 hook / 引言 / narrative 段落用口語仍然合理——這是情境化的取捨、不是精度永遠優於可讀性。"><code>colloquial-rhetoric</code></a>（口語修辭 keyword bank）、<a href="/blog/report/prose-self-contained-without-code-reference/" data-link-title="商業邏輯論述要 self-contained：不依賴 code 才能被理解" data-link-desc="技術文章在「不放 code 的段落」仍然要 self-contained——商業邏輯論述不能預設讀者已經看過 code、用「那個 payload 第二段」「剛才的變數」這類 reference 等於把理解門檻轉嫁給讀者去翻 code。修法是把 reference 翻譯成「用名詞 / 角色 / 條件描述」的 self-contained 句子、即使讀者跳過所有 code block 也能理解論述。"><code>prose-self-contained</code></a>（reader simulation 自測）、<a href="/blog/report/multi-pass-review-frame-granularity-blindspot/" data-link-title="Multi-pass review 的 frame 顆粒度盲點：抽象規則 → 具體訊號的轉譯不完整" data-link-desc="Multi-pass review 跑了 4 輪、字句層問題（口語修辭 / 地區用語 / 依賴 code / 廢話前綴）仍漏 catch——揭露 frame 顆粒度盲點：抽象規則（如「機會成本語氣」「正向陳述」「最重要的話優先說」）沒被轉譯成具體訊號（如 grep keyword bank：「一輩子 / 碰巧 / 撞牆 / 下次 X 時 / 不是 A 而是 B」）。修法是把每條規則展開成可 grep 的 keyword bank、加 reader simulation 輪、加 self-criticism 輪。"><code>multi-pass-review-frame-granularity</code></a>（meta 層、self-criticism 三機制論述）
<strong>Version</strong>: 0.7.6 — 規則四補進階段「避免 hindsight 論述汙染判讀記錄」、輪 5 補設計檢討類另掃 hindsight 論述；新增內部 principle <a href="/blog/report/design-flaw-by-current-axes-not-hindsight/" data-link-title="設計檢討用當下三軸論證、不依賴 hindsight" data-link-desc="本卡提倡用「當下成本對稱條件下選了限制更高的選項」當設計缺陷的判定方式、避免 hindsight 論述把需求演化誤判成設計缺陷。當下三軸論證（成本對稱性 / 可逆性 / 領域先驗）讓判斷不依賴結局發生、且歸因偏向工具預設與制度而非個人預見性。"><code>design-flaw-by-current-axes-not-hindsight</code></a>——把「設計缺陷的精準定義」從「沒預測到後來需求」改成「在當下成本對稱條件下選了限制更高的選項」、用三軸論證代替 hindsight、確保產出 portable 判斷工具
<strong>Version</strong>: 0.7.5 — 將翻譯 / 轉譯 review 升級為獨立 reference：<code>translation-review.md</code>，聚焦句內邏輯對位、前提檢查、因果檢查與讀者追問方向；writing-articles 保留短檢查表並轉介。
<strong>Version</strong>: 0.7.4 — 強化術語翻譯的句內邏輯檢查：譯名放回原句後要檢查主詞、動詞、修飾語、因果與讀者追問方向；避免中文順口但讓句子多出原文沒有的前提。
<strong>Version</strong>: 0.7.3 — 輪 4 補術語檢查：翻譯術語第一次出現保留原文錨點，中文壓縮術語保留完整名詞頭；新增 <code>terminology-keeps-original-anchor</code> 與 <code>compressed-chinese-terms-need-head-noun</code> 兩張內部 principle。
<strong>Version</strong>: 0.7.2 — 補 metadata / navigation surface review：title、description、heading、link label、MOC / index entry、slug / filename 先列入 surface enumeration，再跟正文跑同一輪對意圖、正向陳述與 grep-ability pass；新增內部 principle 連結，維持 skill 可攜性
<strong>Version</strong>: 0.7.1 — 修正 0.7.0 的兩個違規：(a) 移除對外部 content path 的跨引用（違反 reference-authoring-standards 自包含性、修正後段內就地展開層次論述）；(b)「不能用視覺修補替代邏輯或語意修正」改機會成本語氣（違反規則七絕對主義）；標題「層次意識」副題改成「frame 是 horizontal、layer 是 vertical」更精準描述兩軸正交；表格「檢查時機」欄改「修法」更實用、修正「對齐」錯字；加反例段落（emoji 症狀堆疊 vs 改結構）。本 reference 自包含、不引用外部內容系統
<strong>Version</strong>: 0.7.0 — 補強 multi-pass review：第 2 輪檢查清單新增「層次意識」（去掉視覺標記後還能讀嗎、有無依賴 emoji/顏色/圖表）；新增「層次意識」段落說明邏輯層 / 語意層 / 視覺層的區別與優先順序
<strong>Version</strong>: 0.6.0 — 從 references 過載的反思：把「跨多篇議題」（拆分判準、三類 structure 模板、跨篇引用 idiom）整合搬到 <code>managing-article-collections.md</code>；本 reference 聚焦「單篇文章內部怎麼寫」、瘦身 130 行；舊規則八 / 九 整合到那邊。新增規則八「自我應用 (dogfooding)」 — 教某條規則的段落本身遵守該規則
<strong>Version</strong>: 0.5.0 — 從批量改寫 35 篇的經驗回流：規則七補強（選項數由議題決定不強湊、真反模式直接標明、抽象層 / Pattern 卡片不寫「設計取捨 A/B/C/D」）；新增規則九「三類文章用三種 structure」（情境 / 抽象 / Pattern 各自的段落 template）；自檢清單新增五項
<strong>Version</strong>: 0.4.0 — 新增規則七「機會成本語氣」（程式設計極少絕對正確、討論的是多目標取捨；方案對照用 A/B/C/D 多選項並列）；新增規則八「focus 是議題完整度」（拆分判準是 focus、不是邊界清晰；review 必須讀內文）；自檢清單新增四項
<strong>Version</strong>: 0.3.0 — 新增規則六「反例段落用正向陳述」（排除式定義無法建立概念錨點；反例段落仍需正向概念層）；自檢清單新增兩項
<strong>Version</strong>: 0.2.0 — 新增規則五「最重要的話優先說」（資訊優先序；對應核心原則 Explicit Intent；針對 AI 生成文章主次不分反模式）
<strong>Version</strong>: 0.1.0 — 初版（整合外部 methodology + 補充與核心原則的映射）</p>
]]></content:encoded></item><item><title>Writing Code Comments — 程式碼註解撰寫指引</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/writing-code-comments/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/writing-code-comments/</guid><description>&lt;p>本 reference 提供撰寫程式碼註解的完整指引，整合核心寫作原則在註解情境的具體應用。讀者只需讀本文件，即可獨立寫出合格的註解，不需再讀其他 reference。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>核心命題&lt;/strong>：註解是業務需求和設計意圖的守護者。註解的認知依賴必須跟著程式依賴一起降低——介面的註解只寫契約，不洩漏實作；實作的註解說明業務規則，不描述語法選擇。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="適用時機">適用時機&lt;/h2>
&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>Doc comment（&lt;code>///&lt;/code>、&lt;code>/** */&lt;/code>、&lt;code>&amp;quot;&amp;quot;&amp;quot;...&amp;quot;&amp;quot;&amp;quot;&lt;/code>）&lt;/td>
 &lt;td>位於函式、類別、介面、模組宣告前方，供外部閱讀者和工具消費&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Inline comment（&lt;code>//&lt;/code>、&lt;code>#&lt;/code>）&lt;/td>
 &lt;td>位於實作內部，說明「為什麼這樣做」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>模組 README 或檔案頂部 header&lt;/td>
 &lt;td>描述此模組解決的問題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>介面 / 抽象類別的 docstring&lt;/td>
 &lt;td>定義契約（做什麼、輸入輸出語意、使用情境）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>不適用&lt;/strong>：commit message、PR description、ticket context（另見其他情境 reference）。&lt;/p>
&lt;hr>
&lt;h2 id="原則一原子化--註解">原則一：原子化 × 註解&lt;/h2>
&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-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 【需求】UC-003 書籍狀態轉換
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 書籍從「閱讀中」改為「已完成」時，自動將進度設為 100%
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 約束：不可覆蓋使用者手動設定的進度值
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="n">markBookCompleted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BookId&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* ... */&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>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 【需求】UC-003 書籍狀態轉換
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 書籍從「未開始」改為「閱讀中」時，初始化進度為 0%
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 約束：不重置已有的閱讀時間紀錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="n">markBookReading&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BookId&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* ... */&lt;/span> &lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每則註解只描述一個狀態轉換規則，閱讀者不需同時記住兩條規則才能理解一個函式。&lt;/p>
&lt;h3 id="反例">反例&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 【需求】UC-003 書籍狀態轉換
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 處理所有狀態變化：未開始→閱讀中時進度設 0%、閱讀中→完成時進度設 100%、
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 完成→未開始時清空進度、任何狀態→暫停時保留進度並記錄暫停時間
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 約束：不覆蓋使用者手動進度，除了清空場景
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 例外：管理員可跳過轉換規則直接設定任何狀態
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="n">handleBookStatusChange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BookId&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Status&lt;/span> &lt;span class="n">from&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Status&lt;/span> &lt;span class="n">to&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* ... */&lt;/span> &lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>註解承擔 4 種狀態轉換的全部規則，閱讀者認知負擔爆表（規則數 × 例外數 = 複合爆炸），函式本身也違反單一職責。&lt;/p>
&lt;h3 id="判斷標準">判斷標準&lt;/h3>
&lt;ul>
&lt;li>註解超過 5 行且包含 2+ 個「且」「或」「除了」「同時」？→ 拆分函式&lt;/li>
&lt;li>同一註解內有多個無關的約束條件？→ 拆分函式&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="原則二索引--註解">原則二：索引 × 註解&lt;/h2>
&lt;p>&lt;strong>需求編號（UC-XXX、BR-XXX、TKT-ID）是註解的「連結卡」&lt;/strong>，讓註解與規格文件、ticket、worklog 之間建立可追溯的索引。&lt;/p>
&lt;h3 id="正例-1">正例&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 【需求】UC-004 書籍搜尋功能
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 【規格】docs/spec/search.md#fuzzy-match
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 支援書名、作者、標籤的模糊搜尋
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 擴展指引：新增搜尋條件時必須更新 SearchCriteria 值物件
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Book&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">searchBooks&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">SearchCriteria&lt;/span> &lt;span class="n">criteria&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* ... */&lt;/span> &lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>閱讀者可從 UC-004 跳到需求文件，從規格路徑跳到設計細節。三者互為索引卡片。&lt;/p>
&lt;h3 id="反例-1">反例&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 實作書籍搜尋功能，根據某些條件查找
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Book&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">searchBooks&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">SearchCriteria&lt;/span> &lt;span class="n">criteria&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* ... */&lt;/span> &lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>沒有需求索引，閱讀者無法回溯「這個函式為什麼存在」「規格在哪」。&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>需求來源&lt;/td>
 &lt;td>&lt;code>【需求】UC-003&lt;/code> 或 &lt;code>【需求】BR-007&lt;/code>&lt;/td>
 &lt;td>連回需求規格&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>規格文件&lt;/td>
 &lt;td>&lt;code>【規格】docs/spec/xxx.md#section&lt;/code>&lt;/td>
 &lt;td>連回設計細節&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>相依模組&lt;/td>
 &lt;td>&lt;code>【相依】[ModuleA], [ModuleB]&lt;/code>&lt;/td>
 &lt;td>用命名引用，不解釋實作&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>禁止&lt;/strong>：註解內放 ticket ID（如 &lt;code>v1.2.3-W5-001&lt;/code>、&lt;code>TKT-1234&lt;/code>），ticket 是臨時追蹤編號，會被搬移歸檔；需求編號（UC/BR）才是穩定索引。&lt;!-- portability-allow: educational example of prohibited pattern -->&lt;/p>
&lt;hr>
&lt;h2 id="原則三意圖顯性與商業邏輯--註解">原則三：意圖顯性與商業邏輯 × 註解&lt;/h2>
&lt;p>&lt;strong>Doc comment 描述業務情境，不解釋語法選擇。&lt;/strong> 閱讀者想知道「這段程式解決什麼業務問題」「什麼情境下會觸發」「如果不這樣做會發生什麼產品層面後果」；語法細節（while vs if、async、late 變數）讀者看 code 就能推斷，不需要 doc comment 佔用最靠近視線的位置解釋。&lt;/p>
&lt;h3 id="31-語法-vs-業務情境區分">3.1 語法 vs 業務情境區分&lt;/h3>
&lt;p>註解的語法層 vs 業務層區分：&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>語法層（禁止在 doc comment）&lt;/td>
 &lt;td>為什麼用 &lt;code>while&lt;/code> 而不是 &lt;code>if&lt;/code>？為什麼 &lt;code>late&lt;/code>？為什麼 &lt;code>async&lt;/code>？&lt;/td>
 &lt;td>讀 code 可推斷，寫在 doc 浪費視線位置&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>業務層（doc comment 聚焦）&lt;/td>
 &lt;td>此程式解決什麼業務問題？什麼情境觸發？不這樣做會發生什麼產品後果？&lt;/td>
 &lt;td>「印表機鎖定時，後續任務必須排隊等待；若跳過鎖定會導致列印頁面交錯」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h4 id="反例語法選擇解釋">反例：語法選擇解釋&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 使用 while 迴圈而非 if，因為 job queue 可能有多筆任務需要依序處理
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 使用 async 以避免阻塞 UI thread
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1">/// 使用 late 變數因為 printer 物件在建構時尚未就緒
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">Future&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">processPrintJobs&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kd">async&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="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">jobs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">isNotEmpty&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* ... */&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="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這三行都在解釋語法選擇，讀者看 code 已能推斷 &lt;code>async&lt;/code>、&lt;code>late&lt;/code>、&lt;code>while&lt;/code> 的技術動機。沒有任何業務資訊。&lt;/p></description><content:encoded><![CDATA[<p>本 reference 提供撰寫程式碼註解的完整指引，整合核心寫作原則在註解情境的具體應用。讀者只需讀本文件，即可獨立寫出合格的註解，不需再讀其他 reference。</p>
<blockquote>
<p><strong>核心命題</strong>：註解是業務需求和設計意圖的守護者。註解的認知依賴必須跟著程式依賴一起降低——介面的註解只寫契約，不洩漏實作；實作的註解說明業務規則，不描述語法選擇。</p></blockquote>
<hr>
<h2 id="適用時機">適用時機</h2>
<p>當你準備寫 / 改 / 審查以下任一註解時，應用本指引：</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Doc comment（<code>///</code>、<code>/** */</code>、<code>&quot;&quot;&quot;...&quot;&quot;&quot;</code>）</td>
          <td>位於函式、類別、介面、模組宣告前方，供外部閱讀者和工具消費</td>
      </tr>
      <tr>
          <td>Inline comment（<code>//</code>、<code>#</code>）</td>
          <td>位於實作內部，說明「為什麼這樣做」</td>
      </tr>
      <tr>
          <td>模組 README 或檔案頂部 header</td>
          <td>描述此模組解決的問題</td>
      </tr>
      <tr>
          <td>介面 / 抽象類別的 docstring</td>
          <td>定義契約（做什麼、輸入輸出語意、使用情境）</td>
      </tr>
  </tbody>
</table>
<p><strong>不適用</strong>：commit message、PR description、ticket context（另見其他情境 reference）。</p>
<hr>
<h2 id="原則一原子化--註解">原則一：原子化 × 註解</h2>
<p><strong>一則註解只解釋一個概念。</strong> 一個函式若需要註解說明多個不相關的關注點，應先拆分函式，再為每個函式寫獨立註解。</p>
<h3 id="正例">正例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 【需求】UC-003 書籍狀態轉換
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 書籍從「閱讀中」改為「已完成」時，自動將進度設為 100%
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 約束：不可覆蓋使用者手動設定的進度值
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">markBookCompleted</span><span class="p">(</span><span class="n">BookId</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
</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 class="c1">/// 【需求】UC-003 書籍狀態轉換
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">/// 書籍從「未開始」改為「閱讀中」時，初始化進度為 0%
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1">/// 約束：不重置已有的閱讀時間紀錄
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">markBookReading</span><span class="p">(</span><span class="n">BookId</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span></span></span></code></pre></div><p>每則註解只描述一個狀態轉換規則，閱讀者不需同時記住兩條規則才能理解一個函式。</p>
<h3 id="反例">反例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 【需求】UC-003 書籍狀態轉換
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 處理所有狀態變化：未開始→閱讀中時進度設 0%、閱讀中→完成時進度設 100%、
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 完成→未開始時清空進度、任何狀態→暫停時保留進度並記錄暫停時間
</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="kt">void</span> <span class="n">handleBookStatusChange</span><span class="p">(</span><span class="n">BookId</span> <span class="n">id</span><span class="p">,</span> <span class="n">Status</span> <span class="n">from</span><span class="p">,</span> <span class="n">Status</span> <span class="n">to</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span></span></span></code></pre></div><p>註解承擔 4 種狀態轉換的全部規則，閱讀者認知負擔爆表（規則數 × 例外數 = 複合爆炸），函式本身也違反單一職責。</p>
<h3 id="判斷標準">判斷標準</h3>
<ul>
<li>註解超過 5 行且包含 2+ 個「且」「或」「除了」「同時」？→ 拆分函式</li>
<li>同一註解內有多個無關的約束條件？→ 拆分函式</li>
</ul>
<hr>
<h2 id="原則二索引--註解">原則二：索引 × 註解</h2>
<p><strong>需求編號（UC-XXX、BR-XXX、TKT-ID）是註解的「連結卡」</strong>，讓註解與規格文件、ticket、worklog 之間建立可追溯的索引。</p>
<h3 id="正例-1">正例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 【需求】UC-004 書籍搜尋功能
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 【規格】docs/spec/search.md#fuzzy-match
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 支援書名、作者、標籤的模糊搜尋
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">/// 擴展指引：新增搜尋條件時必須更新 SearchCriteria 值物件
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">searchBooks</span><span class="p">(</span><span class="n">SearchCriteria</span> <span class="n">criteria</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span></span></span></code></pre></div><p>閱讀者可從 UC-004 跳到需求文件，從規格路徑跳到設計細節。三者互為索引卡片。</p>
<h3 id="反例-1">反例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 實作書籍搜尋功能，根據某些條件查找
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">searchBooks</span><span class="p">(</span><span class="n">SearchCriteria</span> <span class="n">criteria</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span></span></span></code></pre></div><p>沒有需求索引，閱讀者無法回溯「這個函式為什麼存在」「規格在哪」。</p>
<h3 id="索引內容建議">索引內容建議</h3>
<table>
  <thead>
      <tr>
          <th>索引類型</th>
          <th>格式</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>需求來源</td>
          <td><code>【需求】UC-003</code> 或 <code>【需求】BR-007</code></td>
          <td>連回需求規格</td>
      </tr>
      <tr>
          <td>規格文件</td>
          <td><code>【規格】docs/spec/xxx.md#section</code></td>
          <td>連回設計細節</td>
      </tr>
      <tr>
          <td>相依模組</td>
          <td><code>【相依】[ModuleA], [ModuleB]</code></td>
          <td>用命名引用，不解釋實作</td>
      </tr>
  </tbody>
</table>
<p><strong>禁止</strong>：註解內放 ticket ID（如 <code>v1.2.3-W5-001</code>、<code>TKT-1234</code>），ticket 是臨時追蹤編號，會被搬移歸檔；需求編號（UC/BR）才是穩定索引。<!-- portability-allow: educational example of prohibited pattern --></p>
<hr>
<h2 id="原則三意圖顯性與商業邏輯--註解">原則三：意圖顯性與商業邏輯 × 註解</h2>
<p><strong>Doc comment 描述業務情境，不解釋語法選擇。</strong> 閱讀者想知道「這段程式解決什麼業務問題」「什麼情境下會觸發」「如果不這樣做會發生什麼產品層面後果」；語法細節（while vs if、async、late 變數）讀者看 code 就能推斷，不需要 doc comment 佔用最靠近視線的位置解釋。</p>
<h3 id="31-語法-vs-業務情境區分">3.1 語法 vs 業務情境區分</h3>
<p>註解的語法層 vs 業務層區分：</p>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>註解回答的問題</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>語法層（禁止在 doc comment）</td>
          <td>為什麼用 <code>while</code> 而不是 <code>if</code>？為什麼 <code>late</code>？為什麼 <code>async</code>？</td>
          <td>讀 code 可推斷，寫在 doc 浪費視線位置</td>
      </tr>
      <tr>
          <td>業務層（doc comment 聚焦）</td>
          <td>此程式解決什麼業務問題？什麼情境觸發？不這樣做會發生什麼產品後果？</td>
          <td>「印表機鎖定時，後續任務必須排隊等待；若跳過鎖定會導致列印頁面交錯」</td>
      </tr>
  </tbody>
</table>
<h4 id="反例語法選擇解釋">反例：語法選擇解釋</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 使用 while 迴圈而非 if，因為 job queue 可能有多筆任務需要依序處理
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 使用 async 以避免阻塞 UI thread
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 使用 late 變數因為 printer 物件在建構時尚未就緒
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">processPrintJobs</span><span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="k">while</span> <span class="p">(</span><span class="n">jobs</span><span class="p">.</span><span class="n">isNotEmpty</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這三行都在解釋語法選擇，讀者看 code 已能推斷 <code>async</code>、<code>late</code>、<code>while</code> 的技術動機。沒有任何業務資訊。</p>
<h4 id="正例業務情境聚焦">正例：業務情境聚焦</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 【需求】UC-008 列印佇列管理
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 印表機一次只能處理一份任務；若同時送出多份會造成頁面交錯（使用者投訴 #234）。
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 此函式確保所有 pending 任務依送出順序依序處理，直到佇列清空。
</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">/// 【相依】[PrinterLockService]
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">processPrintJobs</span><span class="p">()</span> <span class="kd">async</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span></span></span></code></pre></div><p>讀完可理解：業務情境（印表機獨佔）、觸發條件（多份任務同時送出）、失敗後果（頁面交錯 + 使用者投訴）、約束（外部鎖）。技術細節（async/while）由 code 自明。</p>
<h3 id="32-doc-comment-聚焦業務情境">3.2 Doc comment 聚焦業務情境</h3>
<p>Doc comment 是函式、類別、介面的契約說明，應回答三個問題：</p>
<ol>
<li><strong>解決什麼業務問題</strong>：此程式存在的商業原因</li>
<li><strong>什麼情境觸發</strong>：使用者的哪個動作、系統的哪個狀態會進入此路徑</li>
<li><strong>不這樣做的產品後果</strong>：錯誤實作會造成什麼使用者可見的問題</li>
</ol>
<h4 id="反例純技術描述">反例：純技術描述</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 將 Book 物件序列化為 JSON 字串並寫入 SharedPreferences
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 使用 jsonEncode 和 keyPrefix = &#39;book_&#39;
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">saveBook</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span></span></span></code></pre></div><p>只描述「怎麼做」，沒說「為什麼做」「什麼時候該做」。</p>
<h4 id="正例業務情境驅動">正例：業務情境驅動</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 【需求】UC-002 離線書庫
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 使用者關閉 app 後重開仍能看到書庫清單（不需重新從 Readmoo 抓取）。
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 觸發時機：使用者匯入新書、編輯書籍標籤、標記進度時。
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">/// 若未持久化，使用者每次開 app 都要等網路載入，離線時完全無法使用。
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">saveBook</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span></span></span></code></pre></div><h3 id="33-禁止-doc-comment-寫-todo--placeholder">3.3 禁止 Doc comment 寫 TODO / placeholder</h3>
<p>Doc comment 描述穩定契約，不應包含「等等要做 X」「暫時這樣」「未來會改」等臨時性內容。TODO/placeholder 應該：</p>
<table>
  <thead>
      <tr>
          <th>內容</th>
          <th>放哪裡</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>未完成工作</td>
          <td>Ticket 或 inline comment（<code>// TODO: ...</code>）</td>
      </tr>
      <tr>
          <td>暫時實作說明</td>
          <td>Inline comment，並建立 ticket 追蹤</td>
      </tr>
      <tr>
          <td>臨時 workaround</td>
          <td>Inline comment 指向 issue link</td>
      </tr>
      <tr>
          <td>穩定契約</td>
          <td>Doc comment</td>
      </tr>
  </tbody>
</table>
<h4 id="反例-2">反例</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 【需求】UC-005 閱讀進度同步
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// TODO: 之後要加入衝突解決邏輯
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 暫時實作：直接覆蓋本地值，不處理同時間多裝置修改
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">/// 注意：此函式還沒寫單元測試（placeholder）
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">syncProgress</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span></span></span></code></pre></div><p>Doc comment 是契約，卻充滿「之後」「暫時」「還沒」——讀者無法判斷哪些是契約、哪些是待辦。</p>
<h4 id="正例-2">正例</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 【需求】UC-005 閱讀進度同步
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 將本地進度寫入雲端。同時間多裝置修改時，採 last-write-wins。
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 約束：網路失敗時本地變更會留在 outbox 等待重試（呼叫端不需處理失敗）
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">syncProgress</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
</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 class="c1">// TODO(&lt;ticket-id&gt;): last-write-wins 是暫時策略，實作 vector clock 衝突解決
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="err">暫時不寫測試，</span><span class="o">&lt;</span><span class="n">ticket</span><span class="o">-</span><span class="n">id</span><span class="o">&gt;</span> <span class="err">追蹤</span></span></span></code></pre></div><p>Doc 只描述當前契約（LWW 策略是<strong>當前的契約</strong>，不是「暫時」），TODO 放 inline 並指向 ticket。</p>
<h3 id="34-註解貼合抽象層級">3.4 註解貼合抽象層級</h3>
<p><strong>介面的註解只寫契約，不洩漏實作。</strong> 程式刻意解耦（介面 vs 實作、模組 vs 模組）是為了讓讀/改某一層時不需通盤理解其他層；若註解又把實作細節拉回介面裡，讀介面等於要先認識實作，抽象帶來的好處被註解抵消掉。</p>
<p><strong>核心原則</strong>：註解的認知依賴必須跟著程式依賴一起降低。</p>
<h4 id="反例介面-doc-洩漏實作">反例：介面 doc 洩漏實作</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">OrderRepository</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="c1">/// 取得訂單清單。
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 資料來源由實作層決定（目前為定時輪詢 [IOnlineOrderService]），
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 消費端不需關心來源為輪詢或推播。
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Order</span><span class="o">&gt;&gt;</span> <span class="n">fetchOrders</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>既說「不需關心」又主動告知「目前是輪詢」，自相矛盾且洩漏實作。讀者看完反而<strong>被迫知道</strong>實作是輪詢，介面的抽象價值被註解抵消。</p>
<h4 id="正例只寫契約--命名引用">正例：只寫契約 + 命名引用</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">OrderRepository</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="c1">/// 取得訂單清單。
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 保證按建立時間由新到舊排序。空結果回傳空陣列（不拋例外）。
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 資料來源：[IOnlineOrderService]
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Order</span><span class="o">&gt;&gt;</span> <span class="n">fetchOrders</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>讀者想知道資料來源細節可跳轉到 <code>IOnlineOrderService</code>；不想知道可跳過。註解的認知依賴和程式的依賴方向一致（介面 → 服務命名引用，不反向揭露實作）。</p>
<h4 id="同理適用範圍">同理適用範圍</h4>
<table>
  <thead>
      <tr>
          <th>位置</th>
          <th>只寫什麼</th>
          <th>不寫什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>介面 doc</td>
          <td>契約（做什麼、輸入輸出語意、使用情境）</td>
          <td>「目前實作用什麼方式」</td>
      </tr>
      <tr>
          <td>模組 README</td>
          <td>此模組解決的問題、對外 API</td>
          <td>內部類別結構、用了什麼演算法</td>
      </tr>
      <tr>
          <td>函式 docstring</td>
          <td>解決什麼問題、輸入輸出約定</td>
          <td>內部怎麼解決（除非行為細節是契約，如冪等性、順序保證）</td>
      </tr>
      <tr>
          <td>抽象類別 doc</td>
          <td>子類需實作的契約、行為承諾</td>
          <td>某個子類的具體實作方式</td>
      </tr>
  </tbody>
</table>
<p><strong>例外</strong>：當行為細節本身就是契約的一部分時（例如「保證冪等」「保證依序處理」「保證不阻塞」），這些細節<strong>就是契約</strong>，必須寫在 doc 中。關鍵判斷：消費端會因此細節而改變使用方式嗎？是 → 寫進 doc；否 → 留給實作層。</p>
<hr>
<h2 id="原則四可查詢性--註解">原則四：可查詢性 × 註解</h2>
<p><strong>註解的關鍵字設計讓 grep / AI 能定位。</strong> 當維護者搜尋「哪個函式處理書籍狀態轉換」時，註解應包含讓搜尋命中的詞彙。</p>
<h3 id="關鍵字設計建議">關鍵字設計建議</h3>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>格式</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>需求索引</td>
          <td><code>【需求】UC-XXX</code></td>
          <td><code>【需求】UC-003 書籍狀態轉換</code></td>
      </tr>
      <tr>
          <td>業務概念</td>
          <td>使用業務詞彙而非技術詞彙</td>
          <td>用「狀態轉換」而不只寫「update」</td>
      </tr>
      <tr>
          <td>分類標記</td>
          <td><code>【事件】</code>、<code>【約束】</code>、<code>【相依】</code></td>
          <td><code>【事件】BookAddedEvent</code> 處理</td>
      </tr>
      <tr>
          <td>警告標記</td>
          <td><code>【警告】</code>、<code>【維護】</code></td>
          <td><code>【維護】修改前檢查 3 個呼叫端</code></td>
      </tr>
  </tbody>
</table>
<h3 id="正例-3">正例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 【需求】UC-006 借閱到期提醒
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 【事件】ReminderTriggeredEvent 消費端
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 當借閱書籍距離到期日 &lt; 3 天時觸發提醒。
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">/// 【約束】同一本書 24 小時內只提醒一次（由 ReminderDebouncer 處理）
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">/// 【維護】修改提醒閾值需同步更新 ReminderDebouncer.windowHours
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">sendDueReminder</span><span class="p">(</span><span class="n">Loan</span> <span class="n">loan</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span></span></span></code></pre></div><p>搜尋「到期提醒」、「ReminderTriggeredEvent」、「ReminderDebouncer」都能命中；<code>【事件】</code> 標記讓過濾所有事件消費端變可能。</p>
<h3 id="反例-3">反例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 檢查並處理到期相關的事情
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">sendDueReminder</span><span class="p">(</span><span class="n">Loan</span> <span class="n">loan</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span></span></span></code></pre></div><p>「相關的事情」是語意黑洞，grep 搜尋業務詞彙無法命中。</p>
<h3 id="分隔符規範">分隔符規範</h3>
<p>使用一致的分隔符讓結構可被 regex 解析：</p>
<ul>
<li><code>【XXX】</code> 全形方括號：語義標記（需求、事件、約束、維護、相依）</li>
<li><code>[ModuleName]</code> 半形方括號：類別 / 模組命名引用（可被 LSP 跳轉）</li>
<li><code>UC-XXX</code>、<code>BR-XXX</code>、<code>#section</code>：索引格式固定，便於全專案統一搜尋</li>
</ul>
<hr>
<h2 id="原則五欄位設計--註解不同抽象層的註解寫法">原則五：欄位設計 × 註解（不同抽象層的註解寫法）</h2>
<p><strong>不同抽象層的註解寫法不同</strong>——這是「欄位設計精神」在註解的應用：同一概念在不同位置用不同角度描述，避免重複。</p>
<h3 id="抽象層級對照表">抽象層級對照表</h3>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>註解角度</th>
          <th>回答的問題</th>
          <th>範例重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>介面 / 抽象類別</td>
          <td>契約視角</td>
          <td>做什麼？承諾什麼？</td>
          <td>「保證按建立時間排序」</td>
      </tr>
      <tr>
          <td>實作類別</td>
          <td>策略視角</td>
          <td>用什麼策略達成契約？（策略選擇理由，非語法）</td>
          <td>「用資料庫 ORDER BY 而非記憶體排序，因為訂單數可能 &gt; 10 萬」</td>
      </tr>
      <tr>
          <td>函式</td>
          <td>步驟視角</td>
          <td>此函式在整個流程扮演什麼角色？</td>
          <td>「在轉帳流程中負責扣款步驟」</td>
      </tr>
      <tr>
          <td>Inline（行內）</td>
          <td>決策視角</td>
          <td>這行為何必要？不這樣會怎樣？</td>
          <td><code>// 必須先 flush cache，否則下一行的 read 會拿到舊值</code></td>
      </tr>
  </tbody>
</table>
<h3 id="正例同一業務概念的多層寫法">正例：同一業務概念的多層寫法</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 介面層：只寫契約
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">OrderSearcher</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="c1">/// 【需求】UC-004 訂單查詢
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 依關鍵字搜尋訂單。結果按建立時間新到舊排序。
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 無結果回傳空陣列（不拋例外）。
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Order</span><span class="o">&gt;&gt;</span> <span class="n">searchByKeyword</span><span class="p">(</span><span class="kt">String</span> <span class="n">keyword</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></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">// 實作層：解釋策略選擇
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">DbOrderSearcher</span> <span class="kd">implements</span> <span class="n">OrderSearcher</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="c1">/// 【需求】UC-004 訂單查詢
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 使用資料庫全文索引（非記憶體 filter），因訂單量可能達十萬級。
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 【相依】[FullTextIndex]
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span>  <span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Order</span><span class="o">&gt;&gt;</span> <span class="n">searchByKeyword</span><span class="p">(</span><span class="kt">String</span> <span class="n">keyword</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1">// ... 實作
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span>    <span class="c1">// 先 normalize 輸入（小寫 + 移除標點），否則索引無法命中
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span>    <span class="kd">final</span> <span class="n">normalized</span> <span class="o">=</span> <span class="n">_normalize</span><span class="p">(</span><span class="n">keyword</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1">// ... 查詢
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><ul>
<li>介面註解：讀者想知道契約即足夠</li>
<li>實作註解：讀者想知道「為什麼用 DB 不用記憶體」</li>
<li>Inline 註解：讀者想知道「這行 normalize 為什麼不可刪」</li>
</ul>
<p>三處註解從不同角度描述同一業務概念，不重複。</p>
<h3 id="反例三層寫相同內容">反例：三層寫相同內容</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">OrderSearcher</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="c1">/// 使用資料庫全文索引搜尋訂單，依建立時間排序
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Order</span><span class="o">&gt;&gt;</span> <span class="n">searchByKeyword</span><span class="p">(</span><span class="kt">String</span> <span class="n">keyword</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">}</span>
</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 class="kd">class</span> <span class="nc">DbOrderSearcher</span> <span class="kd">implements</span> <span class="n">OrderSearcher</span> <span class="p">{</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="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Order</span><span class="o">&gt;&gt;</span> <span class="n">searchByKeyword</span><span class="p">(</span><span class="kt">String</span> <span class="n">keyword</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1">// 使用資料庫全文索引搜尋訂單，依建立時間排序
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>    <span class="c1">// ...
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>介面洩漏實作（違反原則 3.4）、三層內容重複（違反 DRY）、實作沒解釋策略選擇（讀者看不到「為什麼用 DB」）。</p>
<hr>
<h2 id="禁止模式清單">禁止模式清單</h2>
<p>以下註解模式<strong>禁止使用</strong>，CR 時應要求修改：</p>
<table>
  <thead>
      <tr>
          <th>禁止模式</th>
          <th>反例</th>
          <th>問題</th>
          <th>替代做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>程式碼翻譯</strong></td>
          <td><code>// 將計數器加 1</code> → <code>counter++</code></td>
          <td>重述程式碼做什麼，違反 DRY</td>
          <td>刪除註解或改寫為「為什麼加 1」</td>
      </tr>
      <tr>
          <td><strong>Doc 寫 TODO</strong></td>
          <td><code>/// TODO: 之後加驗證</code></td>
          <td>契約文件混入待辦</td>
          <td>移到 inline <code>// TODO(TKT-X): ...</code></td>
      </tr>
      <tr>
          <td><strong>Doc 寫 placeholder</strong></td>
          <td><code>/// 暫時先這樣做</code></td>
          <td>契約不應是臨時的</td>
          <td>寫成當前的真實契約，待辦另外追蹤</td>
      </tr>
      <tr>
          <td><strong>語法選擇解釋</strong></td>
          <td><code>// 用 while 因為要處理多筆</code></td>
          <td>讀 code 可推斷，浪費視線位置</td>
          <td>改寫為業務情境（為什麼有多筆）</td>
      </tr>
      <tr>
          <td><strong>技術實作描述</strong></td>
          <td><code>// 使用 Map 快速查找</code></td>
          <td>讀 code 可推斷</td>
          <td>若效能是契約，寫「保證 O(1) 查詢」</td>
      </tr>
      <tr>
          <td><strong>模糊業務描述</strong></td>
          <td><code>// 處理書籍相關邏輯</code></td>
          <td>語意黑洞，grep 無法命中</td>
          <td>具體描述觸發情境和規則</td>
      </tr>
      <tr>
          <td><strong>過時 TODO</strong></td>
          <td><code>// TODO: 加驗證</code>（但驗證已完成）</td>
          <td>誤導維護者</td>
          <td>刪除</td>
      </tr>
      <tr>
          <td><strong>介面洩漏實作</strong></td>
          <td>介面 doc 寫「目前用輪詢」</td>
          <td>破壞抽象，認知依賴爆增</td>
          <td>改為命名引用 + 只寫契約</td>
      </tr>
      <tr>
          <td><strong>無索引業務邏輯</strong></td>
          <td>純技術描述，無 UC/BR 編號</td>
          <td>無法回溯需求</td>
          <td>加 <code>【需求】UC-XXX</code></td>
      </tr>
      <tr>
          <td><strong>冗長混合責任</strong></td>
          <td>一則註解含 4 種狀態轉換規則</td>
          <td>認知負擔爆表</td>
          <td>拆函式 + 拆註解</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="自檢清單寫完註解問自己">自檢清單（寫完註解問自己）</h2>
<ul>
<li><input disabled="" type="checkbox"> 這則註解描述的是業務問題還是語法選擇？（若是後者，刪除或改寫）</li>
<li><input disabled="" type="checkbox"> 若這是 doc comment，讀者不看實作能理解契約嗎？</li>
<li><input disabled="" type="checkbox"> 若這是介面註解，有洩漏任何「目前實作用什麼」嗎？</li>
<li><input disabled="" type="checkbox"> 註解包含 TODO / placeholder / 暫時 嗎？（若是，移到 inline + ticket）</li>
<li><input disabled="" type="checkbox"> 一則註解只解釋一個概念嗎？（若不是，拆函式）</li>
<li><input disabled="" type="checkbox"> 有需求索引（UC/BR）讓閱讀者能回溯嗎？</li>
<li><input disabled="" type="checkbox"> 關鍵字設計讓 grep / AI 能找到嗎？（業務詞彙而非通用詞彙）</li>
<li><input disabled="" type="checkbox"> 註解貼合所在抽象層嗎？（介面寫契約、實作寫策略、inline 寫決策）</li>
</ul>
<hr>
<h2 id="多輪-re-read-passmulti-pass-refinement">多輪 Re-read Pass（multi-pass refinement）</h2>
<p>寫完上方自檢還不是 done — 自檢是「同 frame 的最後一掃」、不是 multi-pass。Multi-pass 要求每輪用<strong>不同 frame</strong> catch 不同層的錯（<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 — 不是所有錯誤都該被攔截。">literal-interception-vs-behavioral-refinement</a> / <a href="/blog/report/writing-multi-pass-review/" data-link-title="Writing 的 multi-pass review：N 輪 review、每輪換 frame" data-link-desc="寫文章 / 註解 / 文件 / prompt 的「寫」不是單次動作 — 是 N 輪 review。第 1 輪生成、第 2 輪對意圖（#67）、第 3 輪檢查機會成本語氣、第 4 輪 grep-ability、第 5 輪反例 / 邊界。每輪不同 frame、單輪寫不出全部維度。本卡是 #82 在「寫」這個 output 動作的具體實例。">writing-multi-pass-review</a>）。</p>
<p>註解用的核心三輪 + 兩輪程式碼專屬：</p>
<table>
  <thead>
      <tr>
          <th>輪</th>
          <th>Frame</th>
          <th>程式碼註解專用 checklist</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>生成</td>
          <td>把「為什麼」寫出來、預期語句不順</td>
      </tr>
      <tr>
          <td>2</td>
          <td>對意圖（<a href="/blog/report/ease-of-writing-vs-intent-alignment/" data-link-title="寫作便利度跟意圖對齊反相關" data-link-desc="寫程式時最容易寫出的版本、通常是離意圖最遠的版本。便利度建立在「現有上下文 / 已 materialize 資料 / 已存在 API」上、而意圖對齊需要找到正確的層、處理上游、跨抽象層 — 兩者方向相反。識別這個反相關 = 識別自己掉進「容易寫的陷阱」。">ease-of-writing-vs-intent-alignment</a>）</td>
          <td>寫的是「為什麼這樣做」嗎、不是「程式在做什麼」？業務需求 vs 語法選擇分清楚？</td>
      </tr>
      <tr>
          <td>3</td>
          <td>機會成本語氣</td>
          <td>「必須」「不可」翻成「在 X 情境下選擇 A 因為 ⋯⋯」</td>
      </tr>
      <tr>
          <td>4'</td>
          <td>介面 vs 實作分層</td>
          <td>doc comment 不洩漏 impl、inline comment 講 why 不講 what、抽象層對齊嗎？</td>
      </tr>
      <tr>
          <td>5'</td>
          <td>時間軸 robust</td>
          <td>5 個月後讀還看得懂嗎？依賴的 ticket / 連結還活著嗎？</td>
      </tr>
  </tbody>
</table>
<h3 id="naming-子場景四輪-reviewnaming-as-iterated-artifact">Naming 子場景：四輪 review（<a href="/blog/report/naming-as-iterated-artifact/" data-link-title="Naming 是 iterated artifact：第一個名字幾乎不對、四輪 review 才收斂" data-link-desc="命名（變數 / 函式 / 檔名 / slug / API endpoint）幾乎沒有「一次寫對」的可能：第一個名字基於當下狹窄的 context、會在後續 cross-call-site / grep / 重構中暴露錯位。命名的正確設計是 iterated — 寫第一版 → grep-ability 測試 → cross-call-site 一致性 → impl 洩漏 → 重命名。本卡是 #83 在「命名」場景的特化。">naming-as-iterated-artifact</a>）</h3>
<p>註解寫完該 review 一次「這段程式碼涉及的命名」。命名是 iterated artifact、第一版幾乎不對：</p>
<table>
  <thead>
      <tr>
          <th>輪</th>
          <th>Frame</th>
          <th>命名專用 checklist</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>第一版</td>
          <td>反映「做什麼 / 是什麼」、不是「怎麼做」；不超過 4 單字</td>
      </tr>
      <tr>
          <td>2</td>
          <td>Grep-ability</td>
          <td><code>grep -r &quot;&lt;name&gt;&quot;</code> 能命中目標、不被別的 entity 蓋過、不撞 framework reserved（<code>data</code> 等）</td>
      </tr>
      <tr>
          <td>3</td>
          <td>Cross-call-site 一致</td>
          <td>同概念在不同 file 用同名嗎？動詞時態一致嗎？跟同 module 命名格式一致嗎？</td>
      </tr>
      <tr>
          <td>4</td>
          <td>Impl 洩漏檢查</td>
          <td>名字含 impl 細節嗎（<code>fetchUserViaSql</code>）？換 impl 後名字還對嗎？data structure 細節洩漏嗎？</td>
      </tr>
  </tbody>
</table>
<p>跳輪規則：</p>
<ul>
<li>Loop counter / close-up var：跑輪 1</li>
<li>Test code 內部 helper：1 + 4</li>
<li>跨 team API / DB schema：每輪都跑、跑兩遍</li>
<li>Production-facing URL / endpoint：不可跳</li>
</ul>
<p>詳見 <a href="/blog/report/naming-as-iterated-artifact/" data-link-title="Naming 是 iterated artifact：第一個名字幾乎不對、四輪 review 才收斂" data-link-desc="命名（變數 / 函式 / 檔名 / slug / API endpoint）幾乎沒有「一次寫對」的可能：第一個名字基於當下狹窄的 context、會在後續 cross-call-site / grep / 重構中暴露錯位。命名的正確設計是 iterated — 寫第一版 → grep-ability 測試 → cross-call-site 一致性 → impl 洩漏 → 重命名。本卡是 #83 在「命名」場景的特化。">naming-as-iterated-artifact</a>。</p>
<hr>
<h2 id="常見意外場景">常見意外場景</h2>
<h3 id="場景-1改-bug-時順手改壞註解">場景 1：改 bug 時順手改壞註解</h3>
<p>修 bug 改了實作，忘記檢查 doc 是否仍成立。<strong>SOP</strong>：改實作前先讀 doc comment；改完後回檢「doc 描述是否仍準確」。若契約變了，doc 必須同步修。</p>
<h3 id="場景-2抄別的函式的-doc-comment">場景 2：抄別的函式的 doc comment</h3>
<p>抄來的 doc 描述可能來自另一個業務情境。<strong>SOP</strong>：抄完後必須重寫需求索引和業務描述，確認「這個函式解決的問題」與原函式相同才能保留 doc。</p>
<h3 id="場景-3用-ai-產生-doc-comment">場景 3：用 AI 產生 doc comment</h3>
<p>AI 傾向產生「語法翻譯式」註解（使用 while 因為&hellip;）。<strong>SOP</strong>：AI 產出後必須人工審查，把「語法理由」全刪，補「業務理由」。</p>
<h3 id="場景-4介面加新方法時-copy-實作的-doc">場景 4：介面加新方法時 copy 實作的 doc</h3>
<p>實作的 doc 可能包含策略細節，直接 copy 到介面會洩漏實作。<strong>SOP</strong>：新增介面方法時，只保留契約描述部分（做什麼、輸入輸出、使用情境），刪除策略細節（用什麼演算法、為什麼用 DB）。</p>
<hr>
<h2 id="回顧總結">回顧總結</h2>
<table>
  <thead>
      <tr>
          <th>原則</th>
          <th>在註解情境的具體意義</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>原子化</td>
          <td>一則註解只解釋一個概念；函式需多條規則就拆函式</td>
      </tr>
      <tr>
          <td>索引</td>
          <td>用 UC/BR 編號建立註解 → 需求 → 規格的追溯鏈</td>
      </tr>
      <tr>
          <td>意圖顯性與商業邏輯</td>
          <td>Doc 聚焦業務情境；區分語法 vs 業務；不寫 TODO/placeholder；貼合抽象層級</td>
      </tr>
      <tr>
          <td>可查詢性</td>
          <td>使用業務詞彙、語義標記（<code>【XXX】</code>）、命名引用（<code>[ModuleName]</code>）</td>
      </tr>
      <tr>
          <td>欄位設計</td>
          <td>不同抽象層用不同角度寫註解，介面/實作/函式/inline 各司其職</td>
      </tr>
  </tbody>
</table>
<p>寫合格的註解是讓每個維護者（包括你自己三個月後）能在最短時間內理解業務意圖、避免破壞設計、安全地擴展功能。</p>
]]></content:encoded></item><item><title>Writing Documents — 文件撰寫指引</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/writing-documents/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/writing-documents/</guid><description>&lt;blockquote>
&lt;p>Situation: You are about to compose any markdown document — worklog, README, spec, methodology, error-pattern record, or ticket — where both human cognitive load and downstream AI token consumption matter.&lt;/p>&lt;/blockquote>
&lt;h2 id="before-you-start-identify-document-type">Before You Start: Identify Document Type&lt;/h2>
&lt;p>Different document types have different readers, lifespans, and structural demands. Pick the type first; the rest of this reference applies the five compositional principles to each.&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Document type&lt;/th>
 &lt;th>Primary reader&lt;/th>
 &lt;th>Lifespan&lt;/th>
 &lt;th>Volatility&lt;/th>
 &lt;th>Core job&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Worklog&lt;/td>
 &lt;td>Future self / handoff receiver&lt;/td>
 &lt;td>Per version (archived)&lt;/td>
 &lt;td>High (appended daily)&lt;/td>
 &lt;td>Record decisions and milestones, not execution details&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>README&lt;/td>
 &lt;td>Newcomer / marketplace visitor&lt;/td>
 &lt;td>Permanent&lt;/td>
 &lt;td>Low (stable)&lt;/td>
 &lt;td>Orient readers in one screen, route to deeper docs&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Spec (requirement / use case)&lt;/td>
 &lt;td>Implementers, reviewers, QA&lt;/td>
 &lt;td>Permanent (versioned)&lt;/td>
 &lt;td>Medium (evolves with scope)&lt;/td>
 &lt;td>Define acceptable behaviour in testable terms&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Methodology&lt;/td>
 &lt;td>Framework users (cross-project)&lt;/td>
 &lt;td>Permanent&lt;/td>
 &lt;td>Low (distilled)&lt;/td>
 &lt;td>Give framework users and AI an explicit, directly-applicable judgment standard&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Error-pattern&lt;/td>
 &lt;td>Debuggers, reviewers&lt;/td>
 &lt;td>Permanent&lt;/td>
 &lt;td>Low (append-only)&lt;/td>
 &lt;td>Capture root cause + prevention so it doesn&amp;rsquo;t recur&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ticket&lt;/td>
 &lt;td>Executor, dispatcher&lt;/td>
 &lt;td>Per task (archived)&lt;/td>
 &lt;td>Medium (mutated during execution)&lt;/td>
 &lt;td>Carry a single atomic intent from creation to completion&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Two rules follow from the table:&lt;/p>
&lt;ol>
&lt;li>High-volatility documents (worklog, ticket) must stay append-only; rewriting history breaks the record.&lt;/li>
&lt;li>Permanent documents (methodology, error-pattern, spec) must be distilled; every paragraph earns its keep.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="principle-1--atomization--documents-section-granularity-and-when-to-split">Principle 1 — Atomization × Documents: Section granularity and when to split&lt;/h2>
&lt;h3 id="core-question">Core question&lt;/h3>
&lt;p>How much content belongs in a single file, and when do you cut?&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>Situation: You are about to compose any markdown document — worklog, README, spec, methodology, error-pattern record, or ticket — where both human cognitive load and downstream AI token consumption matter.</p></blockquote>
<h2 id="before-you-start-identify-document-type">Before You Start: Identify Document Type</h2>
<p>Different document types have different readers, lifespans, and structural demands. Pick the type first; the rest of this reference applies the five compositional principles to each.</p>
<table>
  <thead>
      <tr>
          <th>Document type</th>
          <th>Primary reader</th>
          <th>Lifespan</th>
          <th>Volatility</th>
          <th>Core job</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Worklog</td>
          <td>Future self / handoff receiver</td>
          <td>Per version (archived)</td>
          <td>High (appended daily)</td>
          <td>Record decisions and milestones, not execution details</td>
      </tr>
      <tr>
          <td>README</td>
          <td>Newcomer / marketplace visitor</td>
          <td>Permanent</td>
          <td>Low (stable)</td>
          <td>Orient readers in one screen, route to deeper docs</td>
      </tr>
      <tr>
          <td>Spec (requirement / use case)</td>
          <td>Implementers, reviewers, QA</td>
          <td>Permanent (versioned)</td>
          <td>Medium (evolves with scope)</td>
          <td>Define acceptable behaviour in testable terms</td>
      </tr>
      <tr>
          <td>Methodology</td>
          <td>Framework users (cross-project)</td>
          <td>Permanent</td>
          <td>Low (distilled)</td>
          <td>Give framework users and AI an explicit, directly-applicable judgment standard</td>
      </tr>
      <tr>
          <td>Error-pattern</td>
          <td>Debuggers, reviewers</td>
          <td>Permanent</td>
          <td>Low (append-only)</td>
          <td>Capture root cause + prevention so it doesn&rsquo;t recur</td>
      </tr>
      <tr>
          <td>Ticket</td>
          <td>Executor, dispatcher</td>
          <td>Per task (archived)</td>
          <td>Medium (mutated during execution)</td>
          <td>Carry a single atomic intent from creation to completion</td>
      </tr>
  </tbody>
</table>
<p>Two rules follow from the table:</p>
<ol>
<li>High-volatility documents (worklog, ticket) must stay append-only; rewriting history breaks the record.</li>
<li>Permanent documents (methodology, error-pattern, spec) must be distilled; every paragraph earns its keep.</li>
</ol>
<hr>
<h2 id="principle-1--atomization--documents-section-granularity-and-when-to-split">Principle 1 — Atomization × Documents: Section granularity and when to split</h2>
<h3 id="core-question">Core question</h3>
<p>How much content belongs in a single file, and when do you cut?</p>
<h3 id="decision-table">Decision table</h3>
<table>
  <thead>
      <tr>
          <th>Condition</th>
          <th>Action</th>
          <th>Reason</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>One document covers one concept cleanly, body &lt; ~500 lines</td>
          <td>Keep single file</td>
          <td>Atomic at file level</td>
      </tr>
      <tr>
          <td>Body crosses ~500 lines but concept is still one</td>
          <td>Split into sub-sections, not separate files</td>
          <td>File is still atomic</td>
      </tr>
      <tr>
          <td>Body contains two independently retrievable concepts (reader consults only one at a time)</td>
          <td>Split into two files</td>
          <td>Violates atomicity</td>
      </tr>
      <tr>
          <td>Single file is consulted as five different situations (e.g. worklog + spec + methodology stacked)</td>
          <td>Split by situation</td>
          <td>Situation match beats line count</td>
      </tr>
      <tr>
          <td>Document is &lt; 100 lines and only referenced from one place</td>
          <td>Consider merging upward</td>
          <td>Under-atomic, creates navigation cost</td>
      </tr>
  </tbody>
</table>
<h3 id="split-heuristics-by-document-type">Split heuristics by document type</h3>
<ul>
<li><strong>Worklog</strong>: Stays single per version. Append chronologically. Do not split by Wave unless the file exceeds ~2000 lines (rare).</li>
<li><strong>README</strong>: Always single file per directory. Route outward via links; do not fork README for sub-topics.</li>
<li><strong>Spec</strong>: Split by use case (one UC per file). Do not bundle multiple UCs for &ldquo;efficiency&rdquo;.</li>
<li><strong>Methodology</strong>: One methodology per file. If a second concept appears, it is a second methodology.</li>
<li><strong>Error-pattern</strong>: One pattern per file. Multiple root causes → multiple files, cross-linked.</li>
<li><strong>Ticket</strong>: One ticket per file is the atomic contract. Splitting a ticket means creating child tickets, not subdividing the file.</li>
</ul>
<h3 id="anti-patterns">Anti-patterns</h3>
<table>
  <thead>
      <tr>
          <th>Anti-pattern</th>
          <th>What happens</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Stuffing three specs into one &ldquo;spec-v2.md&rdquo;</td>
          <td>Readers scan past unrelated sections; grep lands on wrong context</td>
      </tr>
      <tr>
          <td>Splitting a methodology into methodology-part-1/2/3</td>
          <td>Cross-references explode; readers lose the recall benefit</td>
      </tr>
      <tr>
          <td>Merging two error-patterns because &ldquo;they feel similar&rdquo;</td>
          <td>Prevention measures dilute each other; future query returns ambiguity</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="principle-2--indexing--documents-moc-directory-structure-cross-document-references">Principle 2 — Indexing × Documents: MOC, directory structure, cross-document references</h2>
<h3 id="core-question-1">Core question</h3>
<p>Given many atomic documents, how does a reader find the right one?</p>
<h3 id="three-indexing-mechanisms">Three indexing mechanisms</h3>
<ol>
<li><strong>Directory layout</strong> — the physical grouping implies a taxonomy. Keep depths shallow (≤ 3 levels); deeper trees hide documents.</li>
<li><strong>MOC (Map of Content)</strong> — a landing document that lists siblings with one-line summaries. Every directory with more than 5 documents earns an MOC (README.md or index.md).</li>
<li><strong>Inline cross-reference</strong> — when one document must point to another, use a stable relative path and describe <em>why</em> the reader should click, not just that they can.</li>
</ol>
<h3 id="cross-reference-format">Cross-reference format</h3>
<!-- example: Example column contains literal string samples wrapped in backticks (inline code). Paths like ./validation.md are placeholders, not real links. -->
<table>
  <thead>
      <tr>
          <th>Reference type</th>
          <th>Format</th>
          <th>Example</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Same-directory sibling</td>
          <td>Relative path + intent</td>
          <td><code>See [validation rules](./validation.md) for acceptable field values</code></td>
      </tr>
      <tr>
          <td>Cross-directory</td>
          <td>Full repo-relative path</td>
          <td><code>Detailed flow: rules/decision-tree.md</code></td>
      </tr>
      <tr>
          <td>External (stable)</td>
          <td>URL with context</td>
          <td><code>Anthropic skill spec: https://...</code></td>
      </tr>
      <tr>
          <td>External (volatile: ticket ID, commit hash, worklog path)</td>
          <td><strong>Allowed only in volatile documents</strong></td>
          <td>Never in spec / methodology / error-pattern content</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>A reference without intent (&ldquo;see X.md&rdquo;) is a broken signpost. Always say what the reader gains by clicking.</p></blockquote>
<h3 id="reference-anchors-semantic-titles-not-positional-numbers">Reference anchors: semantic titles, not positional numbers</h3>
<p>引用另一個章節 / 階段 / 條列項時、錨點用語意標題、不用位置編號（「見核心問題」、不是「見 Stage 3」；「如『底線告知協議』所述」、不是「如第 4 點」）。編號是結構排列的 derivation — 插入或搬移一個單位、後續編號全部位移、引用句字面完好、語意卻 silent 指向錯的內容（misdirected 比 broken link 難偵測：連結斷掉會報錯、編號錯位會成功解析到錯的東西）。對應要求：每個結構單位的標題要承載核心意義（「Stage 3：核心問題」、編號只是排序前綴）、引用一律取語意半邊。例外是發布方凍結的編號（RFC 段號、法條）— 那是 fact、可引用。重排結構的 commit 要全 repo 掃編號式引用。完整判準與失效案例見 <a href="/blog/report/reference-by-semantic-title-not-number/" data-link-title="引用章節用語意標題、不用位置編號：編號是結構排列的 derivation、會隨版本漂移" data-link-desc="跨段落、跨檔引用結構單位（章節 / 階段 / 條列項）時、引用語意標題（副標題）、不引用位置編號（Stage 3、第 5 章、第 3 點）。編號是「目前結構排列」的 derivation、不是 fact；結構重排時編號全部位移、引用點不會報錯、而是 silent 指向錯的內容 — 比 broken link 更難偵測。標題的存在意義就是承載可被引用的語意。是 #44 SSoT 在結構引用維度的實例、#93 identifier-as-fact 家族的 sibling、#84 命名承載語意的引用面延伸。">reference-by-semantic-title-not-number</a>。</p>
<p>標題要能當穩定錨、名稱本身得先是純 fact：集合命名（問題清單 / 階段序列 / 原則組）只承載角色與層級、把成員數量留給清單呈現 —「核心七問」加一問就在每個複製過名稱的地方失真、「核心問題」承受任意成員增減。判準與邊界（外部凍結品牌 / 概念閾值可留數字）見 <a href="/blog/report/name-collections-by-role-not-count/" data-link-title="集合命名用角色、不內嵌數量：「核心七問」的七是成員數的 derivation、加一問就全面失真" data-link-desc="「核心七問」「成長六階段」「四大支柱」這類名稱把成員數量烤進名字裡 — 數量是集合當前成員的 derivation、不是集合的語意身分；成員增減時名稱失真、且名稱是被複製最多次的字串、缺陷隨每次引用繁殖。修法：命名只承載角色與層級（核心問題 / 次要問題 / 撞牆階段）、數量讓清單自己呈現。本卡是 #155 的命名端 sibling（#155 修引用端、本卡讓「語意標題是穩定錨」的前提真正成立）、#44 SSoT 在名稱內容的實例、#84 命名檢驗的數量維度。">name-collections-by-role-not-count</a>。</p>
<h3 id="indexing-by-document-type">Indexing by document type</h3>
<ul>
<li><strong>Worklog</strong>: The worklog itself is an index of tickets. Each ticket row points into a detail file.</li>
<li><strong>README</strong>: Is the MOC for its directory. Must list every sibling one-line.</li>
<li><strong>Spec</strong>: Indexed by use case ID. Use cases form a flat catalogue; requirements nest under them.</li>
<li><strong>Methodology</strong>: Indexed by <code>methodologies/README.md</code>. Methodologies do not cross-reference methodologies-of-methodologies; keep one hop deep.</li>
<li><strong>Error-pattern</strong>: Indexed by a stable ID prefix scheme (e.g. category abbreviation + running number). The ID itself is the index key.</li>
<li><strong>Ticket</strong>: Indexed by worklog + <code>ticket track list</code>. CLI is the primary index, not a markdown TOC.</li>
</ul>
<h3 id="anti-patterns-1">Anti-patterns</h3>
<table>
  <thead>
      <tr>
          <th>Anti-pattern</th>
          <th>What happens</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>README that says &ldquo;this folder contains various utilities&rdquo;</td>
          <td>Provides no routing; reader opens every file</td>
      </tr>
      <tr>
          <td>Spec referencing a ticket ID</td>
          <td>Spec stability breaks when ticket is archived</td>
      </tr>
      <tr>
          <td>Methodology A references methodology B which references A</td>
          <td>Circular chase; no real content at the end</td>
      </tr>
      <tr>
          <td>&ldquo;See Stage 3&rdquo; / &ldquo;as listed in item 2&rdquo; pointing into a living document</td>
          <td>Structure reorder shifts numbers; reference silently lands on wrong content</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="principle-3--explicit-intent--business-logic--documents-inverted-pyramid-spec-vs-process">Principle 3 — Explicit Intent &amp; Business Logic × Documents: Inverted pyramid, spec vs process</h2>
<h3 id="core-question-2">Core question</h3>
<p>How do you make the point land in the first paragraph — and how do you separate &ldquo;what the rules are&rdquo; from &ldquo;how we got here&rdquo;?</p>
<h3 id="the-inverted-pyramid">The inverted pyramid</h3>
<p>Put the conclusion first. A reader who stops after the opening paragraph should still leave with the main takeaway.</p>
<table>
  <thead>
      <tr>
          <th>Pyramid level</th>
          <th>Content</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Opening sentence</td>
          <td>One-line answer or rule</td>
      </tr>
      <tr>
          <td>First paragraph</td>
          <td>The concrete action or constraint</td>
      </tr>
      <tr>
          <td>Middle</td>
          <td>Context, reasoning, exceptions</td>
      </tr>
      <tr>
          <td>End</td>
          <td>Historical notes, references</td>
      </tr>
  </tbody>
</table>
<h3 id="spec-vs-process-record--a-mandatory-split">Spec vs process record — a mandatory split</h3>
<table>
  <thead>
      <tr>
          <th>Aspect</th>
          <th>Spec (stable)</th>
          <th>Process record (volatile)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Voice</td>
          <td>Imperative / declarative</td>
          <td>Narrative / chronological</td>
      </tr>
      <tr>
          <td>Tense</td>
          <td>Present (&ldquo;the system must&rdquo;)</td>
          <td>Past (&ldquo;we found that&rdquo;)</td>
      </tr>
      <tr>
          <td>Citations allowed</td>
          <td>Other specs, external standards</td>
          <td>Tickets, commits, worklog entries</td>
      </tr>
      <tr>
          <td>Ages well?</td>
          <td>Yes — designed to outlast implementation</td>
          <td>No — tied to a moment</td>
      </tr>
      <tr>
          <td>Safe to rewrite?</td>
          <td>Yes (versioned)</td>
          <td>No (append-only)</td>
      </tr>
  </tbody>
</table>
<p><strong>Rule</strong>: Never mix the two in one document. A spec paragraph written in past tense is a process note; move it to worklog. A worklog entry that begins &ldquo;the system must&rdquo; is a spec fragment; promote it.</p>
<h3 id="business-logic-not-syntax-translation">Business logic, not syntax translation</h3>
<p>Documents that touch business concepts must describe the <em>why</em>, not the <em>what of the syntax</em>.</p>
<table>
  <thead>
      <tr>
          <th>Description style</th>
          <th>Fits</th>
          <th>Fails</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>&ldquo;Readmoo extractor falls back to alternate selector when primary fails&rdquo;</td>
          <td>Business / design intent</td>
          <td>—</td>
      </tr>
      <tr>
          <td>&ldquo;A try-catch wraps the primary selector call&rdquo;</td>
          <td>—</td>
          <td>Syntax translation, reader could get this from the code</td>
      </tr>
  </tbody>
</table>
<p>Rule: if the sentence describes code mechanics a compiler already enforces, delete it and write the business reason instead.</p>
<h3 id="anti-patterns-2">Anti-patterns</h3>
<table>
  <thead>
      <tr>
          <th>Anti-pattern</th>
          <th>Fix</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Opening with &ldquo;This document describes&hellip;&rdquo; meta-talk</td>
          <td>Delete, start with the actual rule</td>
      </tr>
      <tr>
          <td>Spec paragraph citing a ticket ID (e.g. <code>v{X}-W{Y}-{seq}</code>)</td>
          <td>Move citation to worklog; keep spec ID-free</td>
      </tr>
      <tr>
          <td>Methodology paragraph preserving &ldquo;we used to do X but found Y&rdquo; narrative</td>
          <td>Keep the distilled rule; move the narrative to error-pattern or worklog</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="principle-4--searchability--documents-titles-frontmatter-grep-friendly-structure">Principle 4 — Searchability × Documents: Titles, frontmatter, grep-friendly structure</h2>
<h3 id="core-question-3">Core question</h3>
<p>When a human or AI greps for a concept, does the right document surface, and does the right section surface within it?</p>
<h3 id="title-conventions">Title conventions</h3>
<table>
  <thead>
      <tr>
          <th>Document type</th>
          <th>Title format</th>
          <th>grep target</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Worklog</td>
          <td><code>v{version} {theme} 工作日誌</code></td>
          <td><code>v{X.Y.Z}</code>, <code>工作日誌</code></td>
      </tr>
      <tr>
          <td>README</td>
          <td><code>{directory name}</code> or <code>{concept name}</code></td>
          <td>Directory keyword</td>
      </tr>
      <tr>
          <td>Spec</td>
          <td><code>{UC ID} {behaviour name}</code></td>
          <td>UC ID is the key</td>
      </tr>
      <tr>
          <td>Methodology</td>
          <td><code>{subject} 方法論</code></td>
          <td><code>方法論</code> + subject</td>
      </tr>
      <tr>
          <td>Error-pattern</td>
          <td><code>{ID}: {one-line symptom}</code></td>
          <td>Pattern ID + symptom keyword</td>
      </tr>
      <tr>
          <td>Ticket</td>
          <td><code>{ID} {verb + target}</code></td>
          <td>Ticket ID + verb</td>
      </tr>
  </tbody>
</table>
<h3 id="heading-conventions">Heading conventions</h3>
<ul>
<li>Every H2 heading contains a concept keyword that a reader would actually search for (&ldquo;Split heuristics&rdquo;, not &ldquo;More details&rdquo;).</li>
<li>Avoid generic headings: &ldquo;Overview&rdquo;, &ldquo;Introduction&rdquo;, &ldquo;Details&rdquo;, &ldquo;Notes&rdquo;. They produce grep hits on every document.</li>
<li>Prefer question-form or imperative headings: &ldquo;When to split&rdquo;, &ldquo;How to cite a sibling doc&rdquo;.</li>
</ul>
<h3 id="yaml-frontmatter">YAML frontmatter</h3>
<p>Frontmatter is the most grep-friendly slice of a document. Use it for:</p>
<table>
  <thead>
      <tr>
          <th>Field</th>
          <th>Purpose</th>
          <th>Document types</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>id</code></td>
          <td>Stable anchor</td>
          <td>Ticket, error-pattern, spec</td>
      </tr>
      <tr>
          <td><code>status</code></td>
          <td>Lifecycle state</td>
          <td>Ticket, worklog</td>
      </tr>
      <tr>
          <td><code>type</code></td>
          <td>Taxonomy</td>
          <td>Ticket (IMP / ANA / DOC), error-pattern (PC / IMP / ARCH)</td>
      </tr>
      <tr>
          <td><code>relatedTo</code></td>
          <td>Weak link to siblings</td>
          <td>Ticket, spec</td>
      </tr>
      <tr>
          <td><code>version</code></td>
          <td>Scoping</td>
          <td>Worklog, ticket, spec</td>
      </tr>
      <tr>
          <td><code>tags</code></td>
          <td>Cross-cutting search</td>
          <td>Methodology, error-pattern</td>
      </tr>
  </tbody>
</table>
<p>Frontmatter must be machine-parseable. Never include narrative text in values; keep values atomic (ID strings, statuses, dates).</p>
<h3 id="grep-friendly-body-conventions">Grep-friendly body conventions</h3>
<ul>
<li>Put the keyword in the same line as the assertion: &ldquo;Worklog must be append-only&rdquo; (not &ldquo;It must be append-only&rdquo; on its own line).</li>
<li>When listing rules, repeat the subject: every row of a rule table should contain the rule&rsquo;s noun, not rely on column headers alone.</li>
<li>Use separators (<code>→</code>, <code>:</code>, <code>|</code>) consistently so regex-based tooling can parse structure.</li>
</ul>
<h3 id="anti-patterns-3">Anti-patterns</h3>
<table>
  <thead>
      <tr>
          <th>Anti-pattern</th>
          <th>Fix</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Document with no <code>id</code> field searched by filename only</td>
          <td>Add frontmatter for stable ID</td>
      </tr>
      <tr>
          <td>H2 <code>## Details</code> appearing in 30 documents</td>
          <td>Rename to concept-specific heading</td>
      </tr>
      <tr>
          <td>Rules written as &ldquo;It does X; it does Y&rdquo; (pronoun-only)</td>
          <td>Repeat the subject in each row</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="principle-5--field-design--documents-structural-templates-by-type">Principle 5 — Field Design × Documents: Structural templates by type</h2>
<h3 id="core-question-4">Core question</h3>
<p>What sections must a document of type X contain, and how do those sections differ in angle?</p>
<p>Each field in a template exists to answer a specific question. Two fields answering the same question are a bug.</p>
<h3 id="worklog-template">Worklog template</h3>
<table>
  <thead>
      <tr>
          <th>Section</th>
          <th>Answers</th>
          <th>Angle</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Metadata</td>
          <td>Which version, dates, status?</td>
          <td>Identification</td>
      </tr>
      <tr>
          <td>版本目標</td>
          <td>What did this version set out to do?</td>
          <td>Intent</td>
      </tr>
      <tr>
          <td>進度追蹤</td>
          <td>What happened and when?</td>
          <td>Chronological record</td>
      </tr>
      <tr>
          <td>Phase tables</td>
          <td>Which tickets exist, what state?</td>
          <td>Snapshot</td>
      </tr>
      <tr>
          <td>技術筆記</td>
          <td>What non-ticket decisions were made?</td>
          <td>Lateral knowledge</td>
      </tr>
  </tbody>
</table>
<p>Append-only sections: 進度追蹤, 技術筆記. Mutable: Phase tables (statuses flip).</p>
<h3 id="readme-template">README template</h3>
<table>
  <thead>
      <tr>
          <th>Section</th>
          <th>Answers</th>
          <th>Angle</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Title + one-line purpose</td>
          <td>What is this directory / project?</td>
          <td>Orient</td>
      </tr>
      <tr>
          <td>Sibling index</td>
          <td>What&rsquo;s inside?</td>
          <td>Route</td>
      </tr>
      <tr>
          <td>Quick start (if applicable)</td>
          <td>How do I use this in 60 seconds?</td>
          <td>Action</td>
      </tr>
      <tr>
          <td>Further reading</td>
          <td>Where do I go next?</td>
          <td>Defer</td>
      </tr>
  </tbody>
</table>
<h3 id="spec-template">Spec template</h3>
<table>
  <thead>
      <tr>
          <th>Section</th>
          <th>Answers</th>
          <th>Angle</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>UC metadata</td>
          <td>Stable IDs, priority</td>
          <td>Identification</td>
      </tr>
      <tr>
          <td>Actors</td>
          <td>Who uses this?</td>
          <td>Scope</td>
      </tr>
      <tr>
          <td>Preconditions</td>
          <td>What must be true before?</td>
          <td>Entry guard</td>
      </tr>
      <tr>
          <td>Main flow</td>
          <td>What happens on success?</td>
          <td>Happy path</td>
      </tr>
      <tr>
          <td>Alternative / error flows</td>
          <td>What else can happen?</td>
          <td>Edge cases</td>
      </tr>
      <tr>
          <td>Acceptance criteria</td>
          <td>When is this UC done?</td>
          <td>Test hook</td>
      </tr>
  </tbody>
</table>
<p>Angles must not overlap: preconditions are entry guards, not flows; acceptance criteria are pass/fail, not behaviour descriptions.</p>
<h3 id="methodology-template-from-methodology-writing-core">Methodology template (from methodology-writing core)</h3>
<table>
  <thead>
      <tr>
          <th>Section</th>
          <th>Answers</th>
          <th>Angle</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>核心概念</td>
          <td>What is this methodology in one sentence?</td>
          <td>Recall trigger</td>
      </tr>
      <tr>
          <td>執行步驟</td>
          <td>What do I do, in order?</td>
          <td>Action list</td>
      </tr>
      <tr>
          <td>檢查清單</td>
          <td>How do I verify I did it right?</td>
          <td>Self-check</td>
      </tr>
      <tr>
          <td>Reference</td>
          <td>Where is the full implementation guide?</td>
          <td>Defer to SKILL</td>
      </tr>
  </tbody>
</table>
<p>Length discipline: keep only judgment criteria and core rules in the methodology; move operational flows, code examples, and error-handling to a SKILL. Length is whatever makes the criteria explicit and directly applicable — it is not bounded by reading time.</p>
<h3 id="error-pattern-template">Error-pattern template</h3>
<table>
  <thead>
      <tr>
          <th>Section</th>
          <th>Answers</th>
          <th>Angle</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Symptom</td>
          <td>What did the reader see?</td>
          <td>Recognition</td>
      </tr>
      <tr>
          <td>Root cause</td>
          <td>Why did it happen?</td>
          <td>Diagnosis</td>
      </tr>
      <tr>
          <td>Prevention</td>
          <td>How to stop it next time?</td>
          <td>Forward-looking action</td>
      </tr>
      <tr>
          <td>Detection</td>
          <td>How do we catch it early?</td>
          <td>Tooling / hook</td>
      </tr>
  </tbody>
</table>
<p>Each section answers a different question. Symptom is not cause; cause is not prevention.</p>
<h3 id="ticket-template">Ticket template</h3>
<p>Ticket fields are extensively defined elsewhere (designing-fields.md). Key angles here:</p>
<table>
  <thead>
      <tr>
          <th>Field cluster</th>
          <th>Angle</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>what</code></td>
          <td>Description of the action and target</td>
      </tr>
      <tr>
          <td><code>why</code></td>
          <td>Motivation (distinct from <code>what</code>)</td>
      </tr>
      <tr>
          <td><code>acceptance</code></td>
          <td>Pass/fail criteria (distinct from <code>what</code> and <code>why</code>)</td>
      </tr>
      <tr>
          <td><code>how</code></td>
          <td>Strategy (distinct from <code>what</code> — <code>what</code> is the outcome, <code>how</code> is the path)</td>
      </tr>
  </tbody>
</table>
<p>Overlap between <code>what</code> and <code>why</code> is the most common ticket bug. Keep them at different angles.</p>
<h3 id="anti-patterns-4">Anti-patterns</h3>
<table>
  <thead>
      <tr>
          <th>Anti-pattern</th>
          <th>Fix</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>README lists &ldquo;features&rdquo; and &ldquo;what it does&rdquo; as two sections</td>
          <td>Collapse — same angle</td>
      </tr>
      <tr>
          <td>Spec has &ldquo;preconditions&rdquo; and &ldquo;setup requirements&rdquo; as two sections</td>
          <td>Collapse — same angle</td>
      </tr>
      <tr>
          <td>Ticket <code>what</code> describes motivation</td>
          <td>Move motivation to <code>why</code>, keep <code>what</code> for action</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="worklog-specific-extensions-distilled-from-worklog-writing-methodology">Worklog-Specific Extensions (distilled from worklog-writing methodology)</h2>
<h3 id="tool-compatibility">Tool compatibility</h3>
<p>Markdown table cells in worklogs must not contain multi-byte status emoji (<code>⏳</code>, <code>[SYNC]</code>, <code>[FAIL]</code>, etc.). CLI parsers have hit Rust panics on char-boundary issues. Use plain text:</p>
<table>
  <thead>
      <tr>
          <th>Status</th>
          <th>Plain text</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Waiting</td>
          <td>待處理</td>
      </tr>
      <tr>
          <td>In progress</td>
          <td>進行中</td>
      </tr>
      <tr>
          <td>Done</td>
          <td>已完成</td>
      </tr>
      <tr>
          <td>Cancelled</td>
          <td>取消</td>
      </tr>
      <tr>
          <td>Skipped</td>
          <td>跳過</td>
      </tr>
      <tr>
          <td>Blocked</td>
          <td>阻塞</td>
      </tr>
      <tr>
          <td>Failed</td>
          <td>失敗</td>
      </tr>
  </tbody>
</table>
<p>Emoji is allowed outside table cells (in prose headings).</p>
<h3 id="five-events-that-must-be-logged">Five events that must be logged</h3>
<p>Worklog records <em>decisions and milestones</em>, not execution detail (detail lives in tickets).</p>
<table>
  <thead>
      <tr>
          <th>Event</th>
          <th>Trigger</th>
          <th>Format</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Ticket completed</td>
          <td><code>ticket track complete</code></td>
          <td><code>{date}: {id} 完成 — {summary}</code></td>
      </tr>
      <tr>
          <td>Task split</td>
          <td>Child tickets created</td>
          <td><code>{date}: {parent} 拆分 — {child1}, {child2}</code></td>
      </tr>
      <tr>
          <td>Unplanned finding</td>
          <td>New ticket created mid-execution</td>
          <td><code>{date}: 新增 {id} — {reason}</code></td>
      </tr>
      <tr>
          <td>UC progression</td>
          <td>Group of related tickets done</td>
          <td><code>{date}: UC-XX 步驟 Y 完成 — {outcome}</code></td>
      </tr>
      <tr>
          <td>Blocker / risk</td>
          <td>Block or design issue found</td>
          <td><code>{date}: {id} 阻塞 — {cause}</code></td>
      </tr>
  </tbody>
</table>
<p>Prefer bullet list over table: easier to append, diff-friendly, still readable past 20 entries.</p>
<h3 id="narrative-summary-as-legal-alternative">Narrative summary as legal alternative</h3>
<p>When a session closes many tickets in a sprint, per-ticket bullets become impractical. A narrative summary covering the ticket ID range + key outcomes is acceptable:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### {date}: {theme} 衝刺
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span><span class="k">-</span> {ticket-range} 可觀測性修復：全域錯誤處理、靜默失敗路徑消除
</span></span><span class="line"><span class="ln">3</span><span class="cl">- {ticket-range} 審查結論全部修復：AppLogger 遷移、ErrorHandler 防護</span></span></code></pre></div><p>Priority order: per-ticket bullets &gt; narrative summary &gt; no record. Sprint-style summary is the fallback, not the default.</p>
<hr>
<h2 id="methodology-specific-extensions-distilled-from-methodology-writing">Methodology-Specific Extensions (distilled from methodology-writing)</h2>
<h3 id="reader-is-an-expert">Reader is an expert</h3>
<p>Readers already know the content; they just forgot a detail. Your job is to help them recall, not to teach.</p>
<p>Forbidden phrasings:</p>
<ul>
<li>&ldquo;You should&hellip;&rdquo; (condescending)</li>
<li>&ldquo;Remember that&hellip;&rdquo; (teaching)</li>
<li>&ldquo;The lesson here is&hellip;&rdquo; (sermonising)</li>
</ul>
<p>Replace with description of what was done and what was observed.</p>
<h3 id="length-discipline-criteria-stay-flows-leave">Length discipline: criteria stay, flows leave</h3>
<p>A methodology carries the framework&rsquo;s judgment criteria and core rules, written explicitly enough for a reader — including an AI applying them during development — to use directly. Operational detail, not the criteria themselves, belongs in a SKILL or reference.</p>
<p>If rewriting an old verbose methodology, the test is:</p>
<table>
  <thead>
      <tr>
          <th>Check</th>
          <th>If fail</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Does the file contain a complete operational workflow?</td>
          <td>Move it to a SKILL</td>
      </tr>
      <tr>
          <td>Does it contain runnable code examples or error-handling detail?</td>
          <td>Move to a SKILL</td>
      </tr>
      <tr>
          <td>Are the judgment criteria compressed into hint-like bullets a reader cannot apply directly?</td>
          <td>Restore them explicitly in the methodology</td>
      </tr>
  </tbody>
</table>
<p>The methodology keeps its judgment criteria explicit and applicable; the SKILL holds the full walk-through. Never compress the criteria themselves to save length.</p>
<h3 id="experience-sharing-variant-six-guidelines">Experience-sharing variant (six guidelines)</h3>
<p>When the document is an experience-sharing write-up (not a methodology), six additional rules apply:</p>
<ol>
<li>Share experience, do not teach. No &ldquo;you should&rdquo; / &ldquo;remember to&rdquo;.</li>
<li>State facts, do not dramatise. Avoid &ldquo;totally blew up&rdquo;, &ldquo;I was lazy&rdquo;, etc.</li>
<li>Use functional description, not function names, in prose.</li>
<li>Not every problem is your own fault — don&rsquo;t self-blame for library bugs.</li>
<li>Every case needs discovery → diagnosis → fix, not just &ldquo;problem was X, fixed as Y&rdquo;.</li>
<li>Avoid specific product/SDK names in prose; describe the structural problem.</li>
</ol>
<hr>
<h2 id="self-validation-checklist-run-before-committing-the-document">Self-Validation Checklist (run before committing the document)</h2>
<ul>
<li><input disabled="" type="checkbox"> Document type identified, and structure matches the type&rsquo;s template</li>
<li><input disabled="" type="checkbox"> Opening paragraph states the conclusion / rule (inverted pyramid)</li>
<li><input disabled="" type="checkbox"> Spec content contains no process narrative; process content is not dressed as spec</li>
<li><input disabled="" type="checkbox"> No section duplicates another section&rsquo;s angle (no double-answered questions)</li>
<li><input disabled="" type="checkbox"> Cross-references state <em>why</em> to click, not just <em>where</em> to click</li>
<li><input disabled="" type="checkbox"> No reference to a ticket ID / commit hash / worklog path inside a stable document (spec / methodology / error-pattern)</li>
<li><input disabled="" type="checkbox"> Table cells in worklogs contain plain-text status, not emoji</li>
<li><input disabled="" type="checkbox"> File length is either atomic (&lt; ~500 lines) or the body holds a single concept despite length</li>
<li><input disabled="" type="checkbox"> Headings contain searchable concept keywords (no generic &ldquo;Overview&rdquo; / &ldquo;Details&rdquo;)</li>
<li><input disabled="" type="checkbox"> Frontmatter fields, if present, are atomic and machine-parseable</li>
</ul>
<hr>
<h2 id="multi-pass-re-readrefinement-protocol">Multi-pass Re-read（refinement protocol）</h2>
<p>The checklist above is a single-frame final sweep — not multi-pass. Multi-pass requires each round to use a <strong>different frame</strong> to catch errors at different layers (<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 — 不是所有錯誤都該被攔截。">literal-interception-vs-behavioral-refinement</a> / <a href="/blog/report/writing-multi-pass-review/" data-link-title="Writing 的 multi-pass review：N 輪 review、每輪換 frame" data-link-desc="寫文章 / 註解 / 文件 / prompt 的「寫」不是單次動作 — 是 N 輪 review。第 1 輪生成、第 2 輪對意圖（#67）、第 3 輪檢查機會成本語氣、第 4 輪 grep-ability、第 5 輪反例 / 邊界。每輪不同 frame、單輪寫不出全部維度。本卡是 #82 在「寫」這個 output 動作的具體實例。">writing-multi-pass-review</a>).</p>
<p>For documents (worklog / spec / methodology / error-pattern):</p>
<table>
  <thead>
      <tr>
          <th>Round</th>
          <th>Frame</th>
          <th>Document-specific checklist</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>Generation</td>
          <td>Get content end-to-end; expect rough phrasing</td>
      </tr>
      <tr>
          <td>2</td>
          <td>Intent (<a href="/blog/report/ease-of-writing-vs-intent-alignment/" data-link-title="寫作便利度跟意圖對齊反相關" data-link-desc="寫程式時最容易寫出的版本、通常是離意圖最遠的版本。便利度建立在「現有上下文 / 已 materialize 資料 / 已存在 API」上、而意圖對齊需要找到正確的層、處理上游、跨抽象層 — 兩者方向相反。識別這個反相關 = 識別自己掉進「容易寫的陷阱」。">ease-of-writing-vs-intent-alignment</a>)</td>
          <td>Does the document type match the structure used? Spec / process / methodology not mixed?</td>
      </tr>
      <tr>
          <td>3</td>
          <td>Opportunity-cost tone</td>
          <td>Grep &ldquo;must / should / always / never&rdquo; — translate absolutes to &ldquo;A in scenario X / B in Y&rdquo;</td>
      </tr>
      <tr>
          <td>4</td>
          <td>Grep-ability / naming</td>
          <td>Headings contain concept keywords (not &ldquo;Overview&rdquo;); cross-references explain <em>why</em> to click</td>
      </tr>
      <tr>
          <td>5</td>
          <td>Counter-cases / boundaries</td>
          <td>&ldquo;When not to apply&rdquo; section present? Examples cover edge cases not just happy path?</td>
      </tr>
      <tr>
          <td>6'</td>
          <td>Stability layer</td>
          <td>If this is a stable document (spec/methodology), are ticket IDs / commit hashes scrubbed?</td>
      </tr>
      <tr>
          <td>7'</td>
          <td>Atomic check</td>
          <td>&lt; 500 lines OR single concept despite length? Sections each answer one question?</td>
      </tr>
  </tbody>
</table>
<p>Skip rules: quick worklog notes can skip rounds 4-7&rsquo;; stable specs / methodology should run all rounds twice.</p>
<hr>
<h2 id="quick-routing-by-scenario">Quick Routing by Scenario</h2>
<table>
  <thead>
      <tr>
          <th>You are about to&hellip;</th>
          <th>Jump to</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Write or append a worklog entry</td>
          <td>Principle 1 + Worklog extensions</td>
      </tr>
      <tr>
          <td>Start a README from scratch</td>
          <td>Principle 2 (indexing) + Principle 5 (README template)</td>
      </tr>
      <tr>
          <td>Draft a use-case spec</td>
          <td>Principle 3 (spec vs process) + Principle 5 (spec template)</td>
      </tr>
      <tr>
          <td>Rewrite a bloated methodology</td>
          <td>Methodology extensions (length discipline) + Principle 1 (atomize)</td>
      </tr>
      <tr>
          <td>Record a new error-pattern</td>
          <td>Principle 5 (error-pattern template) + Principle 4 (ID as grep anchor)</td>
      </tr>
      <tr>
          <td>Fill a ticket&rsquo;s fields</td>
          <td>Principle 5 (ticket template), then consult designing-fields.md</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>Writing Logs — log 與結構化輸出撰寫指引</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/writing-logs/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/writing-logs/</guid><description>&lt;p>本文件為「撰寫 log 輸出」情境的完整寫作指引。Log 的讀者是未來的除錯者（人或 AI），寫作目標是讓讀者在最短時間內理解&lt;strong>發生了什麼、在哪裡、影響什麼&lt;/strong>。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：本文件不依賴其他 reference。讀完本文件即可獨立寫出合格 log。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="tldr--五條核心規則">TL;DR — 五條核心規則&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>一條 log 一個事件&lt;/td>
 &lt;td>不合併多事件；迴圈用摘要&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>結構化優先&lt;/td>
 &lt;td>Key-Value 或 JSON；禁止純字串拼接&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>描述業務事件，不描述程式碼位置&lt;/td>
 &lt;td>&lt;code>event=order.created&lt;/code> 而非 &lt;code>Entering function processOrder()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Severity 標準：誰該被叫醒&lt;/td>
 &lt;td>error=立刻處理；warn=定期檢視；info=查詢用；debug=開發用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跨元件必有 correlation ID&lt;/td>
 &lt;td>2+ 元件流程必帶 &lt;code>request_id&lt;/code> / &lt;code>trace_id&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="1-原子化--log--一條-log-一個事件">1. 原子化 × Log — 一條 log 一個事件&lt;/h2>
&lt;h3 id="原則">原則&lt;/h3>
&lt;p>一條 log 記錄&lt;strong>一個可識別的事件&lt;/strong>。不要把多個事件合併成一條；也不要把一個事件拆成多條無法關聯的 log。&lt;/p>
&lt;h3 id="判斷標準">判斷標準&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>條件&lt;/th>
 &lt;th>是否該寫成一條 log&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>這是一個有開始和結束的行動（如「載入檔案」）？&lt;/td>
 &lt;td>是，一條&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>這個行動橫跨多個階段（如「下載 → 驗證 → 儲存」）？&lt;/td>
 &lt;td>每階段一條，用 correlation ID 串接&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>這是迴圈中的重複事件（如「處理第 N 筆」）？&lt;/td>
 &lt;td>摘要一條（「處理 1000 筆完成」），不是每筆一條&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>這是異常發生的瞬間？&lt;/td>
 &lt;td>一條（含完整上下文）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="正確範例">正確範例&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">正確：每個事件一條 log，可獨立理解
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">[INFO] request_id=abc123 user_id=42 event=order.created amount=NTD 1500
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">[INFO] request_id=abc123 event=payment.authorized gateway=stripe
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">[INFO] request_id=abc123 event=order.confirmed duration_ms=342&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="反例">反例&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">[INFO] Order abc123 created for user 42, paid via stripe, confirmed in 342ms&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>問題：&lt;/p>
&lt;ul>
&lt;li>監控系統難以從這條 log 提取「付款成功率」&lt;/li>
&lt;li>若只有付款失敗，這條 log 會變成「半成品」或「根本不輸出」&lt;/li>
&lt;li>無法用 grep 找到「所有 payment.authorized 事件」&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="2-索引--log--結構化-log-設計">2. 索引 × Log — 結構化 log 設計&lt;/h2>
&lt;h3 id="原則-1">原則&lt;/h3>
&lt;p>Log 必須能被&lt;strong>機器聚合&lt;/strong>和&lt;strong>人類搜尋&lt;/strong>。結構化 log（key-value 或 JSON）同時滿足兩者；純文字只滿足人類，且在規模擴大後難以維護。&lt;/p>
&lt;h3 id="兩種結構化格式">兩種結構化格式&lt;/h3>
&lt;h4 id="格式-akey-value人類友善機器可解析">格式 A：Key-Value（人類友善，機器可解析）&lt;/h4>





&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">[INFO] 2026-04-16T12:00:00Z level=info component=checkout event=order.created request_id=abc123 user_id=42 amount=1500 currency=NTD&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>適用場景&lt;/strong>：CLI 工具、開發環境、日誌檔案。&lt;/p>
&lt;p>&lt;strong>寫作要求&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>時間戳固定放最前面（ISO 8601 格式）&lt;/li>
&lt;li>&lt;code>level&lt;/code> &lt;code>component&lt;/code> &lt;code>event&lt;/code> 固定三欄位（後述）&lt;/li>
&lt;li>其餘欄位按「通用 → 業務」順序排列&lt;/li>
&lt;li>欄位名稱全小寫，使用下底線（&lt;code>user_id&lt;/code> 非 &lt;code>userId&lt;/code>）&lt;/li>
&lt;/ul>
&lt;h4 id="格式-bjson機器友善適合日誌平台">格式 B：JSON（機器友善，適合日誌平台）&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;timestamp&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="s2">&amp;#34;2026-04-16T12:00:00Z&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nt">&amp;#34;level&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="s2">&amp;#34;info&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nt">&amp;#34;component&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="s2">&amp;#34;checkout&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nt">&amp;#34;event&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="s2">&amp;#34;order.created&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nt">&amp;#34;request_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="s2">&amp;#34;abc123&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nt">&amp;#34;user_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">42&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nt">&amp;#34;amount&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">1500&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nt">&amp;#34;currency&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="s2">&amp;#34;NTD&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>適用場景&lt;/strong>：生產環境、日誌平台（ELK/Loki/CloudWatch）、需要聚合分析。&lt;/p>
&lt;p>&lt;strong>寫作要求&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>單行 JSON（不要換行，否則無法每行一事件聚合）&lt;/li>
&lt;li>欄位順序不重要（機器會解析），但建議時間戳放第一個便於肉眼掃描&lt;/li>
&lt;li>數值型欄位不加引號（&lt;code>&amp;quot;amount&amp;quot;:1500&lt;/code> 非 &lt;code>&amp;quot;amount&amp;quot;:&amp;quot;1500&amp;quot;&lt;/code>）&lt;/li>
&lt;/ul>
&lt;h3 id="correlation-id跨元件追蹤">Correlation ID（跨元件追蹤）&lt;/h3>
&lt;p>&lt;strong>強制要求&lt;/strong>：任何跨越 2+ 元件或 2+ 行動的流程，必須有 correlation ID。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>Correlation ID 類型&lt;/th>
 &lt;th>範例欄位名&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>HTTP 請求流程&lt;/td>
 &lt;td>請求 ID&lt;/td>
 &lt;td>&lt;code>request_id&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>使用者 session&lt;/td>
 &lt;td>Session ID&lt;/td>
 &lt;td>&lt;code>session_id&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>批次任務&lt;/td>
 &lt;td>Job ID&lt;/td>
 &lt;td>&lt;code>job_id&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跨服務 RPC&lt;/td>
 &lt;td>Trace ID&lt;/td>
 &lt;td>&lt;code>trace_id&lt;/code>（配合 &lt;code>span_id&lt;/code>）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>正確範例&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本文件為「撰寫 log 輸出」情境的完整寫作指引。Log 的讀者是未來的除錯者（人或 AI），寫作目標是讓讀者在最短時間內理解<strong>發生了什麼、在哪裡、影響什麼</strong>。</p>
<blockquote>
<p><strong>自包含聲明</strong>：本文件不依賴其他 reference。讀完本文件即可獨立寫出合格 log。</p></blockquote>
<hr>
<h2 id="tldr--五條核心規則">TL;DR — 五條核心規則</h2>
<table>
  <thead>
      <tr>
          <th>規則</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>一條 log 一個事件</td>
          <td>不合併多事件；迴圈用摘要</td>
      </tr>
      <tr>
          <td>結構化優先</td>
          <td>Key-Value 或 JSON；禁止純字串拼接</td>
      </tr>
      <tr>
          <td>描述業務事件，不描述程式碼位置</td>
          <td><code>event=order.created</code> 而非 <code>Entering function processOrder()</code></td>
      </tr>
      <tr>
          <td>Severity 標準：誰該被叫醒</td>
          <td>error=立刻處理；warn=定期檢視；info=查詢用；debug=開發用</td>
      </tr>
      <tr>
          <td>跨元件必有 correlation ID</td>
          <td>2+ 元件流程必帶 <code>request_id</code> / <code>trace_id</code></td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="1-原子化--log--一條-log-一個事件">1. 原子化 × Log — 一條 log 一個事件</h2>
<h3 id="原則">原則</h3>
<p>一條 log 記錄<strong>一個可識別的事件</strong>。不要把多個事件合併成一條；也不要把一個事件拆成多條無法關聯的 log。</p>
<h3 id="判斷標準">判斷標準</h3>
<table>
  <thead>
      <tr>
          <th>條件</th>
          <th>是否該寫成一條 log</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>這是一個有開始和結束的行動（如「載入檔案」）？</td>
          <td>是，一條</td>
      </tr>
      <tr>
          <td>這個行動橫跨多個階段（如「下載 → 驗證 → 儲存」）？</td>
          <td>每階段一條，用 correlation ID 串接</td>
      </tr>
      <tr>
          <td>這是迴圈中的重複事件（如「處理第 N 筆」）？</td>
          <td>摘要一條（「處理 1000 筆完成」），不是每筆一條</td>
      </tr>
      <tr>
          <td>這是異常發生的瞬間？</td>
          <td>一條（含完整上下文）</td>
      </tr>
  </tbody>
</table>
<h3 id="正確範例">正確範例</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">正確：每個事件一條 log，可獨立理解
</span></span><span class="line"><span class="ln">2</span><span class="cl">[INFO] request_id=abc123 user_id=42 event=order.created amount=NTD 1500
</span></span><span class="line"><span class="ln">3</span><span class="cl">[INFO] request_id=abc123 event=payment.authorized gateway=stripe
</span></span><span class="line"><span class="ln">4</span><span class="cl">[INFO] request_id=abc123 event=order.confirmed duration_ms=342</span></span></code></pre></div><h3 id="反例">反例</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">[INFO] Order abc123 created for user 42, paid via stripe, confirmed in 342ms</span></span></code></pre></div><p>問題：</p>
<ul>
<li>監控系統難以從這條 log 提取「付款成功率」</li>
<li>若只有付款失敗，這條 log 會變成「半成品」或「根本不輸出」</li>
<li>無法用 grep 找到「所有 payment.authorized 事件」</li>
</ul>
<hr>
<h2 id="2-索引--log--結構化-log-設計">2. 索引 × Log — 結構化 log 設計</h2>
<h3 id="原則-1">原則</h3>
<p>Log 必須能被<strong>機器聚合</strong>和<strong>人類搜尋</strong>。結構化 log（key-value 或 JSON）同時滿足兩者；純文字只滿足人類，且在規模擴大後難以維護。</p>
<h3 id="兩種結構化格式">兩種結構化格式</h3>
<h4 id="格式-akey-value人類友善機器可解析">格式 A：Key-Value（人類友善，機器可解析）</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">[INFO] 2026-04-16T12:00:00Z level=info component=checkout event=order.created request_id=abc123 user_id=42 amount=1500 currency=NTD</span></span></code></pre></div><p><strong>適用場景</strong>：CLI 工具、開發環境、日誌檔案。</p>
<p><strong>寫作要求</strong>：</p>
<ul>
<li>時間戳固定放最前面（ISO 8601 格式）</li>
<li><code>level</code> <code>component</code> <code>event</code> 固定三欄位（後述）</li>
<li>其餘欄位按「通用 → 業務」順序排列</li>
<li>欄位名稱全小寫，使用下底線（<code>user_id</code> 非 <code>userId</code>）</li>
</ul>
<h4 id="格式-bjson機器友善適合日誌平台">格式 B：JSON（機器友善，適合日誌平台）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span><span class="nt">&#34;timestamp&#34;</span><span class="p">:</span><span class="s2">&#34;2026-04-16T12:00:00Z&#34;</span><span class="p">,</span><span class="nt">&#34;level&#34;</span><span class="p">:</span><span class="s2">&#34;info&#34;</span><span class="p">,</span><span class="nt">&#34;component&#34;</span><span class="p">:</span><span class="s2">&#34;checkout&#34;</span><span class="p">,</span><span class="nt">&#34;event&#34;</span><span class="p">:</span><span class="s2">&#34;order.created&#34;</span><span class="p">,</span><span class="nt">&#34;request_id&#34;</span><span class="p">:</span><span class="s2">&#34;abc123&#34;</span><span class="p">,</span><span class="nt">&#34;user_id&#34;</span><span class="p">:</span><span class="mi">42</span><span class="p">,</span><span class="nt">&#34;amount&#34;</span><span class="p">:</span><span class="mi">1500</span><span class="p">,</span><span class="nt">&#34;currency&#34;</span><span class="p">:</span><span class="s2">&#34;NTD&#34;</span><span class="p">}</span></span></span></code></pre></div><p><strong>適用場景</strong>：生產環境、日誌平台（ELK/Loki/CloudWatch）、需要聚合分析。</p>
<p><strong>寫作要求</strong>：</p>
<ul>
<li>單行 JSON（不要換行，否則無法每行一事件聚合）</li>
<li>欄位順序不重要（機器會解析），但建議時間戳放第一個便於肉眼掃描</li>
<li>數值型欄位不加引號（<code>&quot;amount&quot;:1500</code> 非 <code>&quot;amount&quot;:&quot;1500&quot;</code>）</li>
</ul>
<h3 id="correlation-id跨元件追蹤">Correlation ID（跨元件追蹤）</h3>
<p><strong>強制要求</strong>：任何跨越 2+ 元件或 2+ 行動的流程，必須有 correlation ID。</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>Correlation ID 類型</th>
          <th>範例欄位名</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>HTTP 請求流程</td>
          <td>請求 ID</td>
          <td><code>request_id</code></td>
      </tr>
      <tr>
          <td>使用者 session</td>
          <td>Session ID</td>
          <td><code>session_id</code></td>
      </tr>
      <tr>
          <td>批次任務</td>
          <td>Job ID</td>
          <td><code>job_id</code></td>
      </tr>
      <tr>
          <td>跨服務 RPC</td>
          <td>Trace ID</td>
          <td><code>trace_id</code>（配合 <code>span_id</code>）</td>
      </tr>
  </tbody>
</table>
<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">[INFO] request_id=abc123 component=api event=request.received path=/orders
</span></span><span class="line"><span class="ln">2</span><span class="cl">[DEBUG] request_id=abc123 component=db event=query.start query=select_orders
</span></span><span class="line"><span class="ln">3</span><span class="cl">[DEBUG] request_id=abc123 component=db event=query.end duration_ms=45 rows=3
</span></span><span class="line"><span class="ln">4</span><span class="cl">[INFO] request_id=abc123 component=api event=response.sent status=200 duration_ms=89</span></span></code></pre></div><p>單一 correlation ID <code>abc123</code> 串起整個請求生命週期。</p>
<hr>
<h2 id="3-意圖顯性--log--描述業務事件而非技術動作">3. 意圖顯性 × Log — 描述業務事件而非技術動作</h2>
<h3 id="原則-2">原則</h3>
<p>Log 訊息描述<strong>發生了什麼業務事件</strong>，而不是<strong>程式碼執行到哪一行</strong>。讀者想知道系統的狀態，不是程式的控制流。</p>
<h3 id="對照表">對照表</h3>
<table>
  <thead>
      <tr>
          <th>技術動作（差）</th>
          <th>業務事件（好）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Entering function processOrder()</code></td>
          <td><code>event=order.processing_started order_id=123</code></td>
      </tr>
      <tr>
          <td><code>if branch taken</code></td>
          <td><code>event=order.payment_method_selected method=credit_card</code></td>
      </tr>
      <tr>
          <td><code>Loop iteration 5/10</code></td>
          <td><code>event=batch.progress processed=5 total=10 job_id=xyz</code></td>
      </tr>
      <tr>
          <td><code>Exception caught</code></td>
          <td><code>event=order.validation_failed reason=invalid_shipping_address order_id=123</code></td>
      </tr>
      <tr>
          <td><code>Returning null</code></td>
          <td><code>event=user.lookup_miss user_id=42 reason=not_found</code></td>
      </tr>
  </tbody>
</table>
<h3 id="錯誤-log-必含上下文">錯誤 log 必含上下文</h3>
<p>錯誤 log 的目的是「提供診斷所需的所有資訊」、不是「告訴讀者出錯了」 — 因為「出錯了」這件事系統其他訊號（HTTP 5xx、alert、使用者抱怨）已經傳達、log 此時的角色是讓除錯者重現問題、所以判準對齊到「重現所需的最小資訊集」。光寫「Failed to process order」沒有 order_id / user_id / 錯誤類型、除錯者要從頭追、log 等於白寫。</p>
<p><strong>必填欄位</strong>（錯誤 log）：</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>範例</th>
          <th>為什麼必要</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>error_type</code></td>
          <td><code>validation_error</code></td>
          <td>分類錯誤，便於聚合統計</td>
      </tr>
      <tr>
          <td><code>error_message</code></td>
          <td><code>shipping_address_invalid</code></td>
          <td>人類可讀的原因</td>
      </tr>
      <tr>
          <td><code>component</code></td>
          <td><code>order-service</code></td>
          <td>定位出錯元件</td>
      </tr>
      <tr>
          <td>業務識別</td>
          <td><code>order_id=123 user_id=42</code></td>
          <td>定位受影響的資料/使用者</td>
      </tr>
      <tr>
          <td>Correlation ID</td>
          <td><code>request_id=abc123</code></td>
          <td>串接事件鏈</td>
      </tr>
      <tr>
          <td>錯誤瞬間的狀態</td>
          <td><code>order_status=pending amount=1500</code></td>
          <td>重現問題時的關鍵資訊</td>
      </tr>
  </tbody>
</table>
<p><strong>可選欄位</strong>：<code>stack_trace</code>（長，建議另存檔）、<code>retry_count</code>、<code>upstream_error</code>。</p>
<h3 id="正確範例-1">正確範例</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">錯誤的錯誤 log：
</span></span><span class="line"><span class="ln">2</span><span class="cl">[ERROR] Failed to process order
</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">正確的錯誤 log：
</span></span><span class="line"><span class="ln">5</span><span class="cl">[ERROR] component=order-service event=order.processing_failed request_id=abc123 order_id=123 user_id=42 error_type=payment_declined error_message=insufficient_funds gateway=stripe order_status=pending amount=1500 retry_count=2</span></span></code></pre></div><p>第二條 log 讓讀者不需要重現問題就能理解：</p>
<ul>
<li>哪個元件出錯（<code>order-service</code>）</li>
<li>哪筆訂單、哪個使用者受影響（<code>order_id=123 user_id=42</code>）</li>
<li>錯誤類型和原因（<code>payment_declined / insufficient_funds</code>）</li>
<li>事件背景（<code>gateway=stripe amount=1500</code>）</li>
<li>已重試幾次（<code>retry_count=2</code>）</li>
</ul>
<hr>
<h2 id="4-可查詢性--log--關鍵字一致性與-severity-分級">4. 可查詢性 × Log — 關鍵字一致性與 Severity 分級</h2>
<h3 id="41-事件名稱的關鍵字一致性">4.1 事件名稱的關鍵字一致性</h3>
<p><strong>強制要求</strong>：<code>event</code> 欄位使用固定命名慣例，讓 grep/聚合工具能精確匹配。</p>
<p><strong>推薦慣例</strong>：<code>&lt;domain&gt;.&lt;action&gt;[_&lt;result&gt;]</code></p>
<table>
  <thead>
      <tr>
          <th>範例</th>
          <th>解釋</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>order.created</code></td>
          <td>訂單被建立（動作完成）</td>
      </tr>
      <tr>
          <td><code>order.creation_failed</code></td>
          <td>訂單建立失敗</td>
      </tr>
      <tr>
          <td><code>payment.authorized</code></td>
          <td>付款授權成功</td>
      </tr>
      <tr>
          <td><code>payment.declined</code></td>
          <td>付款被拒</td>
      </tr>
      <tr>
          <td><code>user.login_attempted</code></td>
          <td>使用者嘗試登入（動作開始）</td>
      </tr>
      <tr>
          <td><code>user.login_succeeded</code></td>
          <td>登入成功</td>
      </tr>
  </tbody>
</table>
<p><strong>禁止不一致命名</strong>：</p>
<table>
  <thead>
      <tr>
          <th>禁止</th>
          <th>原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>order.created</code> 和 <code>OrderCreated</code> 混用</td>
          <td>破壞 grep 精確匹配</td>
      </tr>
      <tr>
          <td><code>payment.ok</code> 和 <code>payment.authorized</code> 同義混用</td>
          <td>聚合時變成兩種事件</td>
      </tr>
      <tr>
          <td><code>event=處理成功</code>（中文）</td>
          <td>跨系統相容性差、regex 難寫</td>
      </tr>
  </tbody>
</table>
<h3 id="42-severity-分級判斷標準">4.2 Severity 分級判斷標準</h3>
<p>Severity 的<strong>唯一標準</strong>是「誰應該被叫醒」 — 因為 severity 在 production 的角色是「分流通知對象」（on-call / 工程師日報 / 查詢用 / 開發 debug）、判準應對齊到「該叫誰」。常被誤用的兩個替代判準都對不上分流目的：「問題嚴重度」會把使用者輸入錯誤判成 info（嚴重度低）、但若需要監控異常率該分到 warn；「發生頻率」會把高頻 debug 訊息誤升為 warn（頻率高）、但實際無人需要被叫醒。</p>
<table>
  <thead>
      <tr>
          <th>Severity</th>
          <th>觸發條件</th>
          <th>必填資訊</th>
          <th>誰該關注</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>error</code></td>
          <td>業務功能失敗且需要介入（使用者看到錯誤、資料遺失風險）</td>
          <td>元件、錯誤類型、業務識別、correlation ID、錯誤瞬間狀態</td>
          <td><strong>on-call 工程師需立刻處理</strong></td>
      </tr>
      <tr>
          <td><code>warn</code></td>
          <td>降級運作、重試成功、非預期但可自行恢復</td>
          <td>元件、警告類型、業務識別、恢復方式</td>
          <td>工程師需定期檢視（日報/週報）</td>
      </tr>
      <tr>
          <td><code>info</code></td>
          <td>業務事件發生（訂單建立、使用者登入、任務完成）</td>
          <td>元件、事件名稱、業務識別</td>
          <td>供查詢使用，不主動通知</td>
      </tr>
      <tr>
          <td><code>debug</code></td>
          <td>程式內部狀態、決策分支、效能測量</td>
          <td>元件、狀態描述、相關變數</td>
          <td>開發者除錯用，生產環境可關閉</td>
      </tr>
  </tbody>
</table>
<h3 id="severity-判斷流程">Severity 判斷流程</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">這條 log 記錄的事件是否需要有人立刻處理？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├─ 是 → error
</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">        ├─ 是 → warn
</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">                ├─ 業務事件（使用者可感知） → info
</span></span><span class="line"><span class="ln">7</span><span class="cl">                └─ 技術細節（內部狀態） → debug</span></span></code></pre></div><h3 id="常見誤用">常見誤用</h3>
<table>
  <thead>
      <tr>
          <th>誤用</th>
          <th>問題</th>
          <th>正確做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Retry 成功寫 <code>error</code></td>
          <td>最終成功不該叫醒 on-call</td>
          <td>寫 <code>warn</code>（記錄重試次數）</td>
      </tr>
      <tr>
          <td>使用者輸入錯誤寫 <code>error</code></td>
          <td>使用者錯誤不是系統問題</td>
          <td>寫 <code>info</code>（業務事件）或 <code>warn</code>（若需監控異常率）</td>
      </tr>
      <tr>
          <td>查詢未命中寫 <code>warn</code></td>
          <td>「查不到」通常是正常業務</td>
          <td>寫 <code>info</code>（<code>user.lookup_miss</code>）</td>
      </tr>
      <tr>
          <td>每次函式進入寫 <code>info</code></td>
          <td>污染業務事件流</td>
          <td>寫 <code>debug</code></td>
      </tr>
  </tbody>
</table>
<h3 id="43-欄位值的可查詢設計">4.3 欄位值的可查詢設計</h3>
<table>
  <thead>
      <tr>
          <th>設計</th>
          <th>好查詢</th>
          <th>壞查詢</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>枚舉值用固定小寫字串</td>
          <td><code>status=pending</code></td>
          <td><code>status=PENDING</code> 和 <code>status=pending</code> 混用</td>
      </tr>
      <tr>
          <td>數值不包單位</td>
          <td><code>duration_ms=342</code></td>
          <td><code>duration=&quot;342ms&quot;</code>（regex 難處理）</td>
      </tr>
      <tr>
          <td>布林用明確字串</td>
          <td><code>is_retry=true</code></td>
          <td><code>is_retry=1</code> 或 <code>is_retry=yes</code> 混用</td>
      </tr>
      <tr>
          <td>陣列用分隔符</td>
          <td><code>tags=urgent,priority,vip</code></td>
          <td><code>tags=[&quot;urgent&quot;,&quot;priority&quot;,&quot;vip&quot;]</code>（key-value 格式中難解析）</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="5-欄位設計--log--不同-severity-的必填欄位">5. 欄位設計 × Log — 不同 Severity 的必填欄位</h2>
<h3 id="通用必填欄位所有-severity">通用必填欄位（所有 severity）</h3>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>範例</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>timestamp</code></td>
          <td><code>2026-04-16T12:00:00Z</code></td>
          <td>ISO 8601 帶時區</td>
      </tr>
      <tr>
          <td><code>level</code></td>
          <td><code>info</code></td>
          <td>小寫，見 Severity 分級</td>
      </tr>
      <tr>
          <td><code>component</code></td>
          <td><code>order-service</code></td>
          <td>元件名稱，kebab-case</td>
      </tr>
      <tr>
          <td><code>event</code></td>
          <td><code>order.created</code></td>
          <td>事件名稱，見 4.1 節</td>
      </tr>
  </tbody>
</table>
<h3 id="severity-特定必填欄位">Severity 特定必填欄位</h3>
<table>
  <thead>
      <tr>
          <th>Severity</th>
          <th>必填欄位</th>
          <th>格式範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>error</code></td>
          <td><code>error_type</code>、<code>error_message</code>、業務識別（至少一個）、Correlation ID（跨元件時）、錯誤瞬間關鍵狀態</td>
          <td><code>[ERROR] timestamp=... level=error component=... event=... error_type=... error_message=... order_id=... request_id=...</code></td>
      </tr>
      <tr>
          <td><code>warn</code></td>
          <td><code>warn_type</code>、業務識別、恢復方式（若有）</td>
          <td><code>[WARN] timestamp=... level=warn component=... event=... warn_type=... order_id=... retry_count=2</code></td>
      </tr>
      <tr>
          <td><code>info</code></td>
          <td>業務識別（讓這條 log 可被單獨查詢）</td>
          <td><code>[INFO] timestamp=... level=info component=... event=... order_id=...</code></td>
      </tr>
      <tr>
          <td><code>debug</code></td>
          <td>無硬性業務欄位；允許記錄內部狀態、分支決策、變數值；必須能在生產環境關閉</td>
          <td><code>[DEBUG] timestamp=... level=debug component=... event=... &lt;狀態欄位&gt;</code></td>
      </tr>
  </tbody>
</table>
<h3 id="禁止記錄的欄位">禁止記錄的欄位</h3>
<table>
  <thead>
      <tr>
          <th>禁止</th>
          <th>原因</th>
          <th>替代</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>密碼、API key、token 完整值</td>
          <td>安全風險</td>
          <td>記錄遮罩後的片段（<code>token_prefix=sk_abc...</code>）</td>
      </tr>
      <tr>
          <td>完整使用者 PII（姓名、地址、信用卡）</td>
          <td>隱私合規</td>
          <td>記錄 ID（<code>user_id=42</code>）</td>
      </tr>
      <tr>
          <td>完整請求/回應 body</td>
          <td>太大、可能含敏感資料</td>
          <td>記錄摘要（<code>body_size_bytes=1024</code>）</td>
      </tr>
      <tr>
          <td>完整 SQL 查詢（含參數）</td>
          <td>可能含使用者資料</td>
          <td>記錄參數化 SQL + 參數雜湊</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="6-反模式對照表">6. 反模式對照表</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>症狀</th>
          <th>正確做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>字串拼接代替結構化欄位</td>
          <td><code>[ERROR] Order 123 for user 42 failed because of insufficient funds at stripe</code> — grep 找不到這條</td>
          <td><code>[ERROR] event=order.failed order_id=123 user_id=42 error_type=payment_declined error_message=insufficient_funds gateway=stripe</code></td>
      </tr>
      <tr>
          <td>Severity 表達「問題嚴重度」而非「應否叫醒 on-call」</td>
          <td><code>[ERROR] User entered wrong password</code> — 使用者輸入錯誤不該觸發 on-call</td>
          <td><code>[INFO] event=user.login_failed user_id=42 reason=wrong_password</code></td>
      </tr>
      <tr>
          <td>Log 訊息依程式碼位置命名而非業務事件</td>
          <td><code>[DEBUG] Entering orderController.create() at line 142</code> — 重構時立刻失效</td>
          <td><code>[DEBUG] event=order.creation.input_validating component=order-controller order_id=pending</code></td>
      </tr>
      <tr>
          <td>迴圈中每次迭代都寫 info log</td>
          <td><code>[INFO] Processing item 1</code> × 1000 條 — 污染 log、掩蓋真實事件</td>
          <td><code>[INFO] event=batch.started job_id=xyz total=1000</code> + 完成時摘要</td>
      </tr>
      <tr>
          <td>錯誤 log 只有 error_message 沒有業務識別</td>
          <td><code>[ERROR] Database connection timeout</code> — 哪個查詢？哪個使用者？無法定位</td>
          <td><code>[ERROR] event=db.query_timeout component=order-service request_id=abc123 query=select_orders timeout_ms=5000</code></td>
      </tr>
      <tr>
          <td>混用時間格式或時區</td>
          <td><code>2026-04-16 12:00:00</code> vs <code>Apr 16 12:00:00</code> — 跨系統聚合無法排序</td>
          <td>統一 ISO 8601 + UTC（<code>2026-04-16T12:00:00Z</code>）</td>
      </tr>
      <tr>
          <td>Stack trace 塞進 log 訊息本體</td>
          <td><code>[ERROR] Failed: at func.a (line 12), at func.b (line 34)...</code> — 訊息被稀釋、工具解析困難</td>
          <td><code>[ERROR] event=order.failed error_type=null_pointer stack_trace_id=st_abc123</code>（stack trace 另存）</td>
      </tr>
      <tr>
          <td>使用主觀形容詞</td>
          <td><code>[WARN] Query is slow</code> — 「慢」沒有量化，無法監控</td>
          <td><code>[WARN] event=db.query_slow duration_ms=3200 threshold_ms=1000 query_name=select_user_orders</code></td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="7-自評檢查清單">7. 自評檢查清單</h2>
<p>寫完一條 log 後，自問以下問題。任一答「否」即需修改。</p>
<h3 id="內容自評">內容自評</h3>
<ul>
<li><input disabled="" type="checkbox"> 半年後陌生工程師能僅憑這條 log 理解發生了什麼嗎？</li>
<li><input disabled="" type="checkbox"> 這條 log 有明確的業務事件名稱（<code>event</code> 欄位）嗎？</li>
<li><input disabled="" type="checkbox"> Severity 是依「誰該被叫醒」選的，不是依「感覺多嚴重」嗎？</li>
<li><input disabled="" type="checkbox"> 錯誤 log 含足夠診斷資訊（錯誤類型、業務識別、瞬間狀態）嗎？</li>
<li><input disabled="" type="checkbox"> 跨元件流程有 correlation ID 串接嗎？</li>
</ul>
<h3 id="格式自評">格式自評</h3>
<ul>
<li><input disabled="" type="checkbox"> 結構化（key-value 或 JSON）而非純字串拼接嗎？</li>
<li><input disabled="" type="checkbox"> 欄位命名一致（小寫 + 下底線）嗎？</li>
<li><input disabled="" type="checkbox"> 時間戳是 ISO 8601 + UTC 嗎？</li>
<li><input disabled="" type="checkbox"> 數值不含單位字元（<code>duration_ms=342</code> 而非 <code>duration=342ms</code>）嗎？</li>
<li><input disabled="" type="checkbox"> 事件名稱符合 <code>&lt;domain&gt;.&lt;action&gt;[_&lt;result&gt;]</code> 慣例嗎？</li>
</ul>
<h3 id="安全自評">安全自評</h3>
<ul>
<li><input disabled="" type="checkbox"> 沒有記錄密碼、完整 token、信用卡號嗎？</li>
<li><input disabled="" type="checkbox"> 沒有記錄完整 PII（改記 ID）嗎？</li>
<li><input disabled="" type="checkbox"> 沒有記錄完整請求/回應 body 嗎？</li>
</ul>
<h3 id="可查詢自評">可查詢自評</h3>
<ul>
<li><input disabled="" type="checkbox"> grep <code>event=&lt;事件名&gt;</code> 能精確找到所有相關 log 嗎？</li>
<li><input disabled="" type="checkbox"> grep <code>&lt;業務識別&gt;=&lt;值&gt;</code> 能找到所有影響該實體的 log 嗎？</li>
<li><input disabled="" type="checkbox"> 日誌聚合工具能從這條 log 提取統計指標嗎？</li>
</ul>
<hr>
<h2 id="8-速查表寫作時在手邊">8. 速查表（寫作時在手邊）</h2>
<h3 id="severity-決策">Severity 決策</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">需要立刻叫醒 on-call？ → error
</span></span><span class="line"><span class="ln">2</span><span class="cl">降級/自行恢復的異常？ → warn
</span></span><span class="line"><span class="ln">3</span><span class="cl">業務事件（使用者可感知）？ → info
</span></span><span class="line"><span class="ln">4</span><span class="cl">程式內部狀態/除錯？ → debug</span></span></code></pre></div><h3 id="必填欄位">必填欄位</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">所有：timestamp, level, component, event
</span></span><span class="line"><span class="ln">2</span><span class="cl">error：+ error_type, error_message, 業務識別, correlation_id
</span></span><span class="line"><span class="ln">3</span><span class="cl">warn：+ warn_type, 業務識別
</span></span><span class="line"><span class="ln">4</span><span class="cl">info：+ 業務識別
</span></span><span class="line"><span class="ln">5</span><span class="cl">debug：無硬性要求</span></span></code></pre></div><h3 id="事件命名">事件命名</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">&lt;domain&gt;.&lt;action&gt;          — 動作完成：order.created
</span></span><span class="line"><span class="ln">2</span><span class="cl">&lt;domain&gt;.&lt;action&gt;_failed   — 動作失敗：order.creation_failed
</span></span><span class="line"><span class="ln">3</span><span class="cl">&lt;domain&gt;.&lt;action&gt;_started  — 動作開始：batch.started
</span></span><span class="line"><span class="ln">4</span><span class="cl">&lt;domain&gt;.&lt;action&gt;ing       — 動作進行中：order.processing（罕用）</span></span></code></pre></div><h3 id="格式選擇">格式選擇</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">開發/CLI/小型系統       → Key-Value
</span></span><span class="line"><span class="ln">2</span><span class="cl">生產/日誌平台/大規模    → JSON（單行）
</span></span><span class="line"><span class="ln">3</span><span class="cl">跨服務追蹤必要         → 加 trace_id / span_id</span></span></code></pre></div><hr>
<h2 id="附錄為什麼-log-寫不好就等於沒寫">附錄：為什麼 log 寫不好就等於沒寫</h2>
<p>好 log 與壞 log 的差距在事件發生時才被感受到。壞 log 的代價：</p>
<table>
  <thead>
      <tr>
          <th>壞 log 症狀</th>
          <th>除錯代價</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>訊息模糊（<code>error happened</code>）</td>
          <td>需要重現問題才能定位</td>
      </tr>
      <tr>
          <td>缺少識別（沒有元件/使用者/請求 ID）</td>
          <td>無法串接跨模組事件</td>
      </tr>
      <tr>
          <td>Severity 用錯（info 當 error）</td>
          <td>監控噪音太多，真實警報被淹沒</td>
      </tr>
      <tr>
          <td>散落格式（半 JSON 半文字）</td>
          <td>無法用工具聚合分析</td>
      </tr>
  </tbody>
</table>
<p><strong>本文件目標</strong>：讓你寫的 log 在半年後被陌生工程師搜尋到時，仍能單憑訊息本身理解事件。</p>
<hr>
<p><strong>Scope</strong>: Log output writing (所有程式語言與框架通用)
<strong>Dependencies</strong>: 無（自包含）
<strong>Last Updated</strong>: 2026-04-18</p>
]]></content:encoded></item><item><title>Writing Prompts — AI prompt / instruction 撰寫指引</title><link>https://tarrragon.github.io/blog/skills/compositional-writing/writing-prompts/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/compositional-writing/writing-prompts/</guid><description>&lt;h2 id="檔案定位">檔案定位&lt;/h2>
&lt;p>本檔案為 &lt;strong>&lt;code>.claude/rules/core/ai-communication-rules.md&lt;/code> 的詳細版庫&lt;/strong>（portability-allow: 本 skill 架構性橋接至框架 auto-load 規則，非可攜性違規）。&lt;/p>
&lt;p>框架級 auto-load 規則保留 90 行骨架（核心原則、強制規則、檢查清單），本檔提供完整 Agent Prompt / Context Bundle 骨架、Token 節省深度策略、情境範例與反模式解析。&lt;/p>
&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>快速對照對話規範（預設每次 session auto-load）&lt;/td>
 &lt;td>&lt;code>.claude/rules/core/ai-communication-rules.md&lt;/code> (portability-allow)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>寫大型 Agent Prompt / Ticket Context Bundle&lt;/td>
 &lt;td>本檔（完整骨架範例）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>深度理解 Token 節省取捨&lt;/td>
 &lt;td>本檔「Token 節省策略」章節&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>新增或修改 Skill reference 的撰寫品質規範&lt;/td>
 &lt;td>&lt;code>references/reference-authoring-standards.md&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="情境定位">情境定位&lt;/h2>
&lt;p>本 reference 為 &lt;code>compositional-writing&lt;/code> skill 的情境應用：撰寫 prompt。&lt;/p>
&lt;p>Prompt 的讀者是 AI 模型，但維護者是人類。寫作目標是在&lt;strong>最少 token 內傳遞最精準的意圖&lt;/strong>，同時維持人類可讀性（prompt 需要被維護和迭代）。與其他寫作情境不同，prompt 的每一個冗詞都是直接成本——但過度精簡又會讓意圖模糊，導致 AI 輸出偏離目標。本文件說明如何平衡 token 成本與意圖清晰度。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼-prompt-寫作需要獨立指引">為什麼 Prompt 寫作需要獨立指引&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>差異點&lt;/th>
 &lt;th>一般文件&lt;/th>
 &lt;th>Prompt&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>主要讀者&lt;/td>
 &lt;td>人類&lt;/td>
 &lt;td>AI 模型&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>高（直接 token 費用）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>語氣偏差代價&lt;/td>
 &lt;td>讀者重讀&lt;/td>
 &lt;td>AI 執行失敗、產出錯誤&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>結構化標記&lt;/td>
 &lt;td>錦上添花&lt;/td>
 &lt;td>必要（AI 靠標記定位）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>核心結論：&lt;strong>Prompt 必須在 token 預算內達成「意圖無歧義 + AI 可快速定位關鍵段落」兩個目標&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h2 id="核心原則在-prompt-情境的應用">核心原則在 Prompt 情境的應用&lt;/h2>
&lt;h3 id="1-原子化atomization-prompt">1. 原子化（Atomization）× Prompt&lt;/h3>
&lt;p>&lt;strong>核心&lt;/strong>：一個 prompt 一個任務目標。&lt;/p>
&lt;p>Prompt 的「原子單位」是「一個可被驗收的任務」、不是「一句話」 — 因為「句子」是語法層的單位、AI 執行 prompt 時的單位是「能否完整交付一個可驗收的結果」。一句話可以跨多個任務（例如「幫我寫測試 + 順便重構 + 檢查安全性」）、AI 接到後分散注意力、每個子任務都做得不完整；反之、多句話描述同一個任務（前提 / 動作 / 驗收條件）仍是一個原子 prompt。判準在「驗收完整性」、不在「句數」。&lt;/p>
&lt;p>&lt;strong>判斷標準&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>Prompt 目標是否可用單一動詞 + 單一受詞描述？&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>可能不原子&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>反例&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="nx">違反原子化&lt;/span>&lt;span class="err">：&lt;/span>&lt;span class="nx">三個任務混在一起&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nx">請閱讀檔案&lt;/span> &lt;span class="nx">X&lt;/span>&lt;span class="err">，&lt;/span>&lt;span class="nx">找出函式&lt;/span> &lt;span class="nx">Y&lt;/span> &lt;span class="nx">的測試覆蓋缺口&lt;/span>&lt;span class="err">，&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nx">補上缺失的測試&lt;/span>&lt;span class="err">，&lt;/span>&lt;span class="nx">並順便重構重複的&lt;/span> &lt;span class="nx">setup&lt;/span> &lt;span class="nx">程式碼&lt;/span>&lt;span class="err">，&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="nx">最後把所有&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span> &lt;span class="nx">改成&lt;/span> &lt;span class="nx">logger&lt;/span>&lt;span class="err">。&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>正確&lt;/strong>（拆為 3 個獨立 prompt 或 3 個明確分段）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="nx">任務&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="err">：&lt;/span>&lt;span class="nx">分析測試覆蓋缺口&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="nx">輸入&lt;/span>&lt;span class="err">：&lt;/span>&lt;span class="nx">檔案&lt;/span> &lt;span class="nx">X&lt;/span> &lt;span class="nx">的函式&lt;/span> &lt;span class="nx">Y&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="nx">輸出&lt;/span>&lt;span class="err">：&lt;/span>&lt;span class="nx">未被測試覆蓋的分支清單&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="nx">任務&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="err">：&lt;/span>&lt;span class="nx">補缺測試&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">輸入&lt;/span>&lt;span class="err">：&lt;/span>&lt;span class="nx">任務&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nx">的缺口清單&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nx">輸出&lt;/span>&lt;span class="err">：&lt;/span>&lt;span class="nx">新增的測試案例&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="nx">任務&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="err">：&lt;/span>&lt;span class="nx">清理&lt;/span>&lt;span class="err">（&lt;/span>&lt;span class="nx">獨立&lt;/span> &lt;span class="nx">ticket&lt;/span>&lt;span class="err">）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nx">輸入&lt;/span>&lt;span class="err">：&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span> &lt;span class="nx">位置清單&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nx">輸出&lt;/span>&lt;span class="err">：&lt;/span>&lt;span class="nx">logger&lt;/span> &lt;span class="nx">替換後的&lt;/span> &lt;span class="nx">diff&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="2-索引建立indexing-prompt">2. 索引建立（Indexing）× Prompt&lt;/h3>
&lt;p>&lt;strong>核心&lt;/strong>：結構化標記讓 AI 快速定位關鍵段落。&lt;/p>
&lt;p>AI 讀 prompt 時會建立「段落角色模型」——哪段是目標、哪段是約束、哪段是輸出格式。如果 prompt 是純散文，AI 必須從頭讀到尾才能判斷每段角色，token 和注意力都被浪費。結構化標記（章節標題、列表、表格、標籤）讓 AI 跳讀定位。&lt;/p>
&lt;p>&lt;strong>結構化標記工具箱&lt;/strong>：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>標記&lt;/th>
 &lt;th>用途&lt;/th>
 &lt;th>AI 解讀&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>## 標題&lt;/code>&lt;/td>
 &lt;td>大段角色分隔（任務 / 約束 / 輸出格式）&lt;/td>
 &lt;td>跳讀定位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>### 子標題&lt;/code>&lt;/td>
 &lt;td>同一大段內的細分&lt;/td>
 &lt;td>細節索引&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Markdown 表格&lt;/td>
 &lt;td>對照型資訊（選項、規則、映射）&lt;/td>
 &lt;td>一次載入多維度&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>有序列表 &lt;code>1./2./3.&lt;/code>&lt;/td>
 &lt;td>步驟順序&lt;/td>
 &lt;td>執行序列&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>無序列表 &lt;code>- &lt;/code>&lt;/td>
 &lt;td>條件集合&lt;/td>
 &lt;td>平行規則&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>XML 標籤 &lt;code>&amp;lt;task&amp;gt;&amp;lt;/task&amp;gt;&lt;/code>&lt;/td>
 &lt;td>明確邊界（用於需要嚴格分隔的區塊）&lt;/td>
 &lt;td>強制邊界&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>程式碼圍欄 &lt;code>```&lt;/code>&lt;/td>
 &lt;td>字面值保護（AI 不會誤解為指令）&lt;/td>
 &lt;td>不執行區域&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>建議章節骨架&lt;/strong>（大型 prompt）：&lt;/p></description><content:encoded><![CDATA[<h2 id="檔案定位">檔案定位</h2>
<p>本檔案為 <strong><code>.claude/rules/core/ai-communication-rules.md</code> 的詳細版庫</strong>（portability-allow: 本 skill 架構性橋接至框架 auto-load 規則，非可攜性違規）。</p>
<p>框架級 auto-load 規則保留 90 行骨架（核心原則、強制規則、檢查清單），本檔提供完整 Agent Prompt / Context Bundle 骨架、Token 節省深度策略、情境範例與反模式解析。</p>
<p><strong>閱讀決策</strong>：</p>
<table>
  <thead>
      <tr>
          <th>需求</th>
          <th>讀哪份</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>快速對照對話規範（預設每次 session auto-load）</td>
          <td><code>.claude/rules/core/ai-communication-rules.md</code> (portability-allow)</td>
      </tr>
      <tr>
          <td>寫大型 Agent Prompt / Ticket Context Bundle</td>
          <td>本檔（完整骨架範例）</td>
      </tr>
      <tr>
          <td>深度理解 Token 節省取捨</td>
          <td>本檔「Token 節省策略」章節</td>
      </tr>
      <tr>
          <td>新增或修改 Skill reference 的撰寫品質規範</td>
          <td><code>references/reference-authoring-standards.md</code></td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="情境定位">情境定位</h2>
<p>本 reference 為 <code>compositional-writing</code> skill 的情境應用：撰寫 prompt。</p>
<p>Prompt 的讀者是 AI 模型，但維護者是人類。寫作目標是在<strong>最少 token 內傳遞最精準的意圖</strong>，同時維持人類可讀性（prompt 需要被維護和迭代）。與其他寫作情境不同，prompt 的每一個冗詞都是直接成本——但過度精簡又會讓意圖模糊，導致 AI 輸出偏離目標。本文件說明如何平衡 token 成本與意圖清晰度。</p>
<hr>
<h2 id="為什麼-prompt-寫作需要獨立指引">為什麼 Prompt 寫作需要獨立指引</h2>
<table>
  <thead>
      <tr>
          <th>差異點</th>
          <th>一般文件</th>
          <th>Prompt</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>主要讀者</td>
          <td>人類</td>
          <td>AI 模型</td>
      </tr>
      <tr>
          <td>次要讀者</td>
          <td>無</td>
          <td>維護者（人類）</td>
      </tr>
      <tr>
          <td>冗餘成本</td>
          <td>低（只佔螢幕）</td>
          <td>高（直接 token 費用）</td>
      </tr>
      <tr>
          <td>語氣偏差代價</td>
          <td>讀者重讀</td>
          <td>AI 執行失敗、產出錯誤</td>
      </tr>
      <tr>
          <td>結構化標記</td>
          <td>錦上添花</td>
          <td>必要（AI 靠標記定位）</td>
      </tr>
  </tbody>
</table>
<p>核心結論：<strong>Prompt 必須在 token 預算內達成「意圖無歧義 + AI 可快速定位關鍵段落」兩個目標</strong>。</p>
<hr>
<h2 id="核心原則在-prompt-情境的應用">核心原則在 Prompt 情境的應用</h2>
<h3 id="1-原子化atomization-prompt">1. 原子化（Atomization）× Prompt</h3>
<p><strong>核心</strong>：一個 prompt 一個任務目標。</p>
<p>Prompt 的「原子單位」是「一個可被驗收的任務」、不是「一句話」 — 因為「句子」是語法層的單位、AI 執行 prompt 時的單位是「能否完整交付一個可驗收的結果」。一句話可以跨多個任務（例如「幫我寫測試 + 順便重構 + 檢查安全性」）、AI 接到後分散注意力、每個子任務都做得不完整；反之、多句話描述同一個任務（前提 / 動作 / 驗收條件）仍是一個原子 prompt。判準在「驗收完整性」、不在「句數」。</p>
<p><strong>判斷標準</strong>：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>若答「是」</th>
          <th>行動</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Prompt 目標是否可用單一動詞 + 單一受詞描述？</td>
          <td>是</td>
          <td>原子</td>
      </tr>
      <tr>
          <td>是否存在兩個以上的「和」連接不同動作？</td>
          <td>是</td>
          <td>拆分</td>
      </tr>
      <tr>
          <td>驗收條件是否可並列表達而非混合？</td>
          <td>是</td>
          <td>可能不原子</td>
      </tr>
  </tbody>
</table>
<p><strong>反例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="err">#</span> <span class="nx">違反原子化</span><span class="err">：</span><span class="nx">三個任務混在一起</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">請閱讀檔案</span> <span class="nx">X</span><span class="err">，</span><span class="nx">找出函式</span> <span class="nx">Y</span> <span class="nx">的測試覆蓋缺口</span><span class="err">，</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">補上缺失的測試</span><span class="err">，</span><span class="nx">並順便重構重複的</span> <span class="nx">setup</span> <span class="nx">程式碼</span><span class="err">，</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nx">最後把所有</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span> <span class="nx">改成</span> <span class="nx">logger</span><span class="err">。</span></span></span></code></pre></div><p><strong>正確</strong>（拆為 3 個獨立 prompt 或 3 個明確分段）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="err">#</span> <span class="nx">任務</span> <span class="mi">1</span><span class="err">：</span><span class="nx">分析測試覆蓋缺口</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">輸入</span><span class="err">：</span><span class="nx">檔案</span> <span class="nx">X</span> <span class="nx">的函式</span> <span class="nx">Y</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">輸出</span><span class="err">：</span><span class="nx">未被測試覆蓋的分支清單</span>
</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"><span class="err">#</span> <span class="nx">任務</span> <span class="mi">2</span><span class="err">：</span><span class="nx">補缺測試</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">輸入</span><span class="err">：</span><span class="nx">任務</span> <span class="mi">1</span> <span class="nx">的缺口清單</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">輸出</span><span class="err">：</span><span class="nx">新增的測試案例</span>
</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 class="err">#</span> <span class="nx">任務</span> <span class="mi">3</span><span class="err">：</span><span class="nx">清理</span><span class="err">（</span><span class="nx">獨立</span> <span class="nx">ticket</span><span class="err">）</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">輸入</span><span class="err">：</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span> <span class="nx">位置清單</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">輸出</span><span class="err">：</span><span class="nx">logger</span> <span class="nx">替換後的</span> <span class="nx">diff</span></span></span></code></pre></div><hr>
<h3 id="2-索引建立indexing-prompt">2. 索引建立（Indexing）× Prompt</h3>
<p><strong>核心</strong>：結構化標記讓 AI 快速定位關鍵段落。</p>
<p>AI 讀 prompt 時會建立「段落角色模型」——哪段是目標、哪段是約束、哪段是輸出格式。如果 prompt 是純散文，AI 必須從頭讀到尾才能判斷每段角色，token 和注意力都被浪費。結構化標記（章節標題、列表、表格、標籤）讓 AI 跳讀定位。</p>
<p><strong>結構化標記工具箱</strong>：</p>
<table>
  <thead>
      <tr>
          <th>標記</th>
          <th>用途</th>
          <th>AI 解讀</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>## 標題</code></td>
          <td>大段角色分隔（任務 / 約束 / 輸出格式）</td>
          <td>跳讀定位</td>
      </tr>
      <tr>
          <td><code>### 子標題</code></td>
          <td>同一大段內的細分</td>
          <td>細節索引</td>
      </tr>
      <tr>
          <td>Markdown 表格</td>
          <td>對照型資訊（選項、規則、映射）</td>
          <td>一次載入多維度</td>
      </tr>
      <tr>
          <td>有序列表 <code>1./2./3.</code></td>
          <td>步驟順序</td>
          <td>執行序列</td>
      </tr>
      <tr>
          <td>無序列表 <code>- </code></td>
          <td>條件集合</td>
          <td>平行規則</td>
      </tr>
      <tr>
          <td>XML 標籤 <code>&lt;task&gt;&lt;/task&gt;</code></td>
          <td>明確邊界（用於需要嚴格分隔的區塊）</td>
          <td>強制邊界</td>
      </tr>
      <tr>
          <td>程式碼圍欄 <code>```</code></td>
          <td>字面值保護（AI 不會誤解為指令）</td>
          <td>不執行區域</td>
      </tr>
  </tbody>
</table>
<p><strong>建議章節骨架</strong>（大型 prompt）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 任務目標
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>（1-2 句話）
</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="gu">## 輸入
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu"></span><span class="k">-</span> 檔案路徑 / 資料內容 / 前置條件
</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"><span class="gu">## 約束
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span><span class="k">-</span> 禁止項
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> 必要項
</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 class="gu">## 輸出格式
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span><span class="k">-</span> 預期產出的結構
</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 class="gu">## 驗收條件
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span>- [ ] 可勾選的具體條件</span></span></code></pre></div><p>小型 prompt 可壓縮為單段，但仍要保留「任務—約束—輸出」三段訊號。</p>
<hr>
<h3 id="3-意圖顯性與商業邏輯explicit-intent--business-logic-prompt">3. 意圖顯性與商業邏輯（Explicit Intent &amp; Business Logic）× Prompt</h3>
<p><strong>核心</strong>：開頭直述任務目標，明確預期輸出格式，告訴 AI 為什麼。</p>
<h4 id="31-開頭即表達意圖">3.1 開頭即表達意圖</h4>
<p>Prompt 第一句就說清楚「要 AI 做什麼」。AI 讀 prompt 時會根據前幾句建立初始任務模型——如果前段是背景鋪陳，AI 可能在建立錯誤假設後才讀到真正目標，導致輸出偏差。</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">我們的系統最近遇到一些效能問題，昨天 profile 發現
</span></span><span class="line"><span class="ln">3</span><span class="cl">BookRepository 的 getAll() 呼叫量暴增，估計是 cache 失效。
</span></span><span class="line"><span class="ln">4</span><span class="cl">我之前試過加 memoization 但沒效果，可能是 key 設計問題。
</span></span><span class="line"><span class="ln">5</span><span class="cl">所以，請幫我重寫 getAll() 的快取層。</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"># 開頭直述目標
</span></span><span class="line"><span class="ln">2</span><span class="cl">任務：重寫 BookRepository.getAll() 的快取層。
</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">- 現有 memoization 的 key 設計有問題
</span></span><span class="line"><span class="ln">6</span><span class="cl">- profile 顯示 getAll() 被大量呼叫</span></span></code></pre></div><h4 id="32-明確輸出格式">3.2 明確輸出格式</h4>
<p>告訴 AI 預期產出結構，而非期待它自行推斷。</p>
<p><strong>反例</strong>：「幫我分析一下這段程式碼」
<strong>正確</strong>：「分析下列程式碼，以 Markdown 表格列出：問題 | 影響 | 建議修正。每個問題另附 3-5 行說明。」</p>
<h4 id="33-告訴-ai-為什麼">3.3 告訴 AI 「為什麼」</h4>
<p>當約束或做法不直觀時，補上理由，避免 AI 基於「合理預設」覆寫規則。</p>
<p><strong>反例</strong>：「不要使用 async/await」
<strong>正確</strong>：「不要使用 async/await（本專案目標環境不支援 ES2017+，只能用 Promise.then）」</p>
<p>沒有理由的禁令，AI 可能在重構時基於「現代寫法更好」的預設還原。</p>
<hr>
<h3 id="4-可查詢性searchability-prompt">4. 可查詢性（Searchability）× Prompt</h3>
<p><strong>核心</strong>：模板設計讓維護者能 grep；變數佔位符命名規範讓 AI 和人類都清楚。</p>
<p>Prompt 不是一次性文字，會被反覆維護和調整。未來 6 個月後，維護者需要 grep 找到「所有派發 engineer 代理人的 prompt」「所有包含 context bundle 的 prompt」。如果 prompt 沒有可搜尋的穩定關鍵字，維護成本會爆炸。</p>
<h4 id="41-穩定關鍵字">4.1 穩定關鍵字</h4>
<p>在 prompt 模板的關鍵位置保留穩定詞彙，讓 grep 能一次命中所有同類 prompt。</p>
<table>
  <thead>
      <tr>
          <th>查詢需求</th>
          <th>穩定關鍵字範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>找所有 ticket 派發 prompt</td>
          <td>在開頭統一寫 <code>Ticket: {id}</code></td>
      </tr>
      <tr>
          <td>找所有驗收條件區段</td>
          <td>統一用 <code>## 驗收條件</code> 標題</td>
      </tr>
      <tr>
          <td>找所有需要 ticket track complete 的 prompt</td>
          <td>統一包含 <code>ticket track complete</code> 字串</td>
      </tr>
  </tbody>
</table>
<h4 id="42-變數佔位符命名規範">4.2 變數佔位符命名規範</h4>
<p>模板中的變數必須用明確且可 grep 的佔位符，避免 <code>{x}</code> <code>{var}</code> 這類模糊名稱。</p>
<table>
  <thead>
      <tr>
          <th>反例</th>
          <th>正確</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>{x}</code></td>
          <td><code>{ticket_id}</code></td>
      </tr>
      <tr>
          <td><code>{name}</code></td>
          <td><code>{agent_name}</code> 或 <code>{file_path}</code></td>
      </tr>
      <tr>
          <td><code>{content}</code></td>
          <td><code>{context_bundle_text}</code></td>
      </tr>
  </tbody>
</table>
<p>佔位符規則：</p>
<ul>
<li>全小寫 + 底線分隔（snake_case）</li>
<li>名詞片語，描述「這個變數代表什麼」</li>
<li>避免縮寫（用 <code>file_path</code> 而非 <code>fp</code>）</li>
</ul>
<h4 id="43-區塊邊界標記">4.3 區塊邊界標記</h4>
<p>大型 prompt 內部若有可重用區塊（例如「約束」「輸出格式」），用標記界定邊界方便剪貼和 diff。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="c">&lt;!-- BEGIN: 通用代理人約束 --&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">-</span> 不修改 src/ 以外的檔案
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> commit 訊息格式：<span class="sb">`&lt;type&gt;({ticket_id}): &lt;summary&gt;`</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c">&lt;!-- END: 通用代理人約束 --&gt;</span></span></span></code></pre></div><hr>
<h3 id="5-欄位設計field-design-prompt">5. 欄位設計（Field Design）× Prompt</h3>
<p><strong>核心</strong>：Context bundle 的每個欄位獨立角度，避免混淆。</p>
<p>Prompt 常見的欄位污染：把「做什麼」「為什麼」「如何驗收」「禁止事項」混在一段。AI 讀這種段落時必須反覆解析每句角色，容易漏掉約束。</p>
<p><strong>標準欄位骨架</strong>（適用 ticket context bundle 或任何結構化派發 prompt）：</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>角度</th>
          <th>內容類型</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>任務</code> / <code>Task</code></td>
          <td>動作</td>
          <td>動詞 + 受詞，一句話</td>
      </tr>
      <tr>
          <td><code>輸入</code> / <code>Inputs</code></td>
          <td>材料</td>
          <td>檔案 / 資料 / 前置條件</td>
      </tr>
      <tr>
          <td><code>約束</code> / <code>Constraints</code></td>
          <td>邊界</td>
          <td>禁止項 + 必要項</td>
      </tr>
      <tr>
          <td><code>驗收</code> / <code>Acceptance</code></td>
          <td>終點判定</td>
          <td>可勾選的條件</td>
      </tr>
      <tr>
          <td><code>背景</code> / <code>Context</code></td>
          <td>理由（可選）</td>
          <td>為什麼選此方案</td>
      </tr>
      <tr>
          <td><code>參考</code> / <code>References</code></td>
          <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">任務：重寫快取層，不要使用 memoization，因為 key 設計有問題，完成後要跑 npm test。</span></span></code></pre></div><p>（動作 + 約束 + 理由 + 驗收全擠在一行）</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">任務：重寫 BookRepository.getAll() 的快取層。
</span></span><span class="line"><span class="ln">2</span><span class="cl">約束：不使用現有 memoization 機制。
</span></span><span class="line"><span class="ln">3</span><span class="cl">背景：memoization 的 key 設計失效，profile 確認大量重複計算。
</span></span><span class="line"><span class="ln">4</span><span class="cl">驗收：npm test 通過；profile 顯示 getAll() 呼叫量下降 &gt; 50%。</span></span></code></pre></div><hr>
<h2 id="token-節省策略">Token 節省策略</h2>
<p>Prompt 的每個 token 都是成本。以下策略在<strong>不傷害意圖清晰度</strong>的前提下節省 token。</p>
<h3 id="策略-1用符號取代連接詞">策略 1：用符號取代連接詞</h3>
<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">如果使用者已經登入並且擁有管理員權限，那麼允許存取，否則回傳 403 錯誤。</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">已登入 AND 管理員 → 允許存取；否則 → 403。</span></span></code></pre></div><p><strong>節省</strong>：約 40% token。符號（<code>AND</code>、<code>→</code>、<code>;</code>）是 AI 可無歧義解讀的通用邏輯標記。</p>
<hr>
<h3 id="策略-2表格取代重複句型">策略 2：表格取代重複句型</h3>
<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">當輸入是 JSON 時，回傳 JSON；當輸入是 YAML 時，回傳 YAML；
</span></span><span class="line"><span class="ln">2</span><span class="cl">當輸入是 CSV 時，回傳 CSV；當輸入格式無法辨識時，回傳錯誤訊息。</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">| 輸入格式 | 輸出 |
</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">| JSON | JSON |
</span></span><span class="line"><span class="ln">4</span><span class="cl">| YAML | YAML |
</span></span><span class="line"><span class="ln">5</span><span class="cl">| CSV | CSV |
</span></span><span class="line"><span class="ln">6</span><span class="cl">| 未知 | 錯誤訊息 |</span></span></code></pre></div><p><strong>節省</strong>：約 35% token。表格消除重複的句型骨架（「當 X 時，回傳 Y」），AI 解析更快。</p>
<hr>
<h3 id="策略-3引用路徑取代完整內容貼入">策略 3：引用路徑取代完整內容貼入</h3>
<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">（貼入 500 行規則文件全文）</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">規則：docs/quality-baseline.md（請閱讀後套用）</span></span></code></pre></div><p><strong>節省</strong>：依文件大小，通常 &gt; 90% token。前提是 AI 能存取該路徑（檔案必須存在於執行環境）。</p>
<p><strong>適用判斷</strong>：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>應貼內容？</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>規則文件已存在於專案</td>
          <td>否，引用路徑</td>
      </tr>
      <tr>
          <td>只用文件中一小段</td>
          <td>否，精確引用章節（<code>file.md#section</code>）</td>
      </tr>
      <tr>
          <td>臨時性規則，不存在於檔案</td>
          <td>是，需貼入</td>
      </tr>
      <tr>
          <td>AI 無法存取該路徑（跨 session / 跨環境）</td>
          <td>是，需貼入</td>
      </tr>
  </tbody>
</table>
<hr>
<h3 id="策略-4刪除客套與鋪陳">策略 4：刪除客套與鋪陳</h3>
<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></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">任務：找出下列程式碼的問題。</span></span></code></pre></div><p><strong>節省</strong>：約 70% token。AI 不需要客套語境——直接給任務比「禮貌請求」更有效。</p>
<hr>
<h3 id="策略-5用預設約定取代顯式枚舉">策略 5：用預設約定取代顯式枚舉</h3>
<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">輸出格式：請用 Markdown 格式，標題使用 # 開頭，列表使用 - 開頭，
</span></span><span class="line"><span class="ln">2</span><span class="cl">程式碼使用三個反引號包起來，表格使用 | 分隔。</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">輸出格式：Markdown。</span></span></code></pre></div><p><strong>節省</strong>：約 80% token。Markdown 的語法細節是通用知識，不需要重新定義。</p>
<p><strong>適用原則</strong>：若約定是通用知識（JSON、Markdown、YAML、常見技術術語），直接用名稱；若是專案特定約定，必須顯式說明。</p>
<hr>
<h2 id="結構化標記範例">結構化標記範例</h2>
<h3 id="範例-aagent-prompt派發代理人">範例 A：Agent Prompt（派發代理人）</h3>
<p>完整 agent prompt 骨架：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">Ticket: {ticket_id}
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">任務: {short_task_description}
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Ticket 路徑: {ticket_file_path}
</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">請 Read Ticket 取得完整 Context Bundle 和驗收條件（位於 Problem Analysis section）。
</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"><span class="gu">## 輸入
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span><span class="k">-</span> 目標檔案：{target_files}
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> 依賴 ticket：{dependency_ticket_ids}
</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 class="gu">## 約束
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span><span class="k">-</span> 不修改 .claude/ 以外的 rules/ 目錄
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> 保留既有測試不動
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> commit 訊息格式：<span class="sb">`&lt;type&gt;({ticket_id}): &lt;summary&gt;`</span>
</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"><span class="gu">## 執行步驟
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu"></span><span class="k">1.</span> <span class="sb">`ticket track claim {ticket_id}`</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">2.</span> 依 Context Bundle 執行實作
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">3.</span> <span class="sb">`ticket track append-log {ticket_id} --section &#34;Solution&#34; &#34;...&#34;`</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">4.</span> <span class="sb">`ticket track complete {ticket_id}`</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu">## 驗收
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> {acceptance_1}
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">- [ ]</span> {acceptance_2}
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu">## 一行摘要回報
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu"></span>格式：「{file_name}:{lines}行 {metric_1}:{N} {metric_2}:{N} 狀態:{status}」</span></span></code></pre></div><p><strong>設計要點</strong>：</p>
<table>
  <thead>
      <tr>
          <th>區塊</th>
          <th>功能</th>
          <th>可搜尋性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Ticket: {id}</code></td>
          <td>穩定關鍵字，grep 能找到所有派發 prompt</td>
          <td>高</td>
      </tr>
      <tr>
          <td><code>## 輸入</code> / <code>## 約束</code> / <code>## 執行步驟</code> / <code>## 驗收</code></td>
          <td>標準化章節，AI 快速定位</td>
          <td>高</td>
      </tr>
      <tr>
          <td>變數佔位符 <code>{ticket_id}</code> 等</td>
          <td>snake_case 自說明</td>
          <td>高</td>
      </tr>
      <tr>
          <td>一行摘要回報格式</td>
          <td>統一回報格式，PM 可 grep 聚合</td>
          <td>高</td>
      </tr>
  </tbody>
</table>
<hr>
<h3 id="範例-bticket-context-bundle">範例 B：Ticket Context Bundle</h3>
<p>Ticket YAML frontmatter 的 Problem Analysis 區塊作為 context bundle：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Problem Analysis
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### Context Bundle
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**執行步驟**</span>:
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">1.</span> <span class="sb">`ticket track claim {ticket_id}`</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">2.</span> Read {dependency_ticket_solution_path} 取得 {needed_info}
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">3.</span> 撰寫 <span class="sb">`{target_file_path}`</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">4.</span> {integration_step}
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">5.</span> 可攜性自檢後 <span class="sb">`ticket track complete`</span>
</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"><span class="gs">**關鍵參考**</span>:
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> {reference_1_description}: <span class="sb">`{reference_1_path}`</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> {reference_2_description}: <span class="sb">`{reference_2_path}`</span>
</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"><span class="gs">**約束**</span>:
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> {constraint_1}
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> {constraint_2}
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">-</span> 一行摘要回報：「{field_1}:{X} {field_2}:{N} 狀態:{status}」
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gu">### 問題根因
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">{root_cause_description}
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu">### 影響範圍
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">{impact_scope}
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="gu">### 相關 Error Pattern
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">{error_pattern_reference_or_none}</span></span></code></pre></div><p><strong>設計要點</strong>：</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>角度</th>
          <th>避免混淆規則</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>執行步驟</td>
          <td>How</td>
          <td>具體命令，不寫理由</td>
      </tr>
      <tr>
          <td>關鍵參考</td>
          <td>Where</td>
          <td>檔案路徑 + 用途，不貼內容</td>
      </tr>
      <tr>
          <td>約束</td>
          <td>Must not / Must</td>
          <td>可驗證的邊界條件</td>
      </tr>
      <tr>
          <td>問題根因</td>
          <td>Why</td>
          <td>因果陳述，不寫解法</td>
      </tr>
      <tr>
          <td>影響範圍</td>
          <td>What</td>
          <td>檔案 / 模組清單</td>
      </tr>
  </tbody>
</table>
<p><strong>Context Bundle 的 DRY 原則</strong>：YAML frontmatter 的 <code>what</code> / <code>why</code> / <code>how</code> 已分角度填寫，Context Bundle 不重複這些欄位的內容，只補「執行細節」與「關鍵參考」。</p>
<hr>
<h3 id="範例-c小型內聯-prompt對話中">範例 C：小型內聯 Prompt（對話中）</h3>
<p>適用於對話中快速派發單一任務：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">任務：檢查 {file} 是否有 {pattern}。
</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></code></pre></div><p><strong>設計要點</strong>：不需要完整章節，但仍保留「任務—輸出—邊界」三層訊號。</p>
<hr>
<h2 id="自檢清單">自檢清單</h2>
<p>撰寫 prompt 完成後，逐項檢查：</p>
<h3 id="原子化">原子化</h3>
<ul>
<li><input disabled="" type="checkbox"> Prompt 目標可用單一動詞 + 單一受詞描述</li>
<li><input disabled="" type="checkbox"> 無多個不相關任務混合</li>
</ul>
<h3 id="索引建立">索引建立</h3>
<ul>
<li><input disabled="" type="checkbox"> 有結構化標記（章節 / 表格 / 列表）</li>
<li><input disabled="" type="checkbox"> 大型 prompt 有「任務 / 輸入 / 約束 / 輸出 / 驗收」骨架</li>
</ul>
<h3 id="意圖顯性">意圖顯性</h3>
<ul>
<li><input disabled="" type="checkbox"> 第一句即表達任務目標</li>
<li><input disabled="" type="checkbox"> 明確指定輸出格式</li>
<li><input disabled="" type="checkbox"> 不直觀的約束有附理由</li>
</ul>
<h3 id="可查詢性">可查詢性</h3>
<ul>
<li><input disabled="" type="checkbox"> 有穩定關鍵字供 grep（ticket ID / 章節名 / 工具名）</li>
<li><input disabled="" type="checkbox"> 變數佔位符為 snake_case 且自說明</li>
<li><input disabled="" type="checkbox"> 可重用區塊有邊界標記（如適用）</li>
</ul>
<h3 id="欄位設計">欄位設計</h3>
<ul>
<li><input disabled="" type="checkbox"> 每個欄位只寫該角度內容</li>
<li><input disabled="" type="checkbox"> 無「動作 + 約束 + 理由 + 驗收」擠在一起</li>
<li><input disabled="" type="checkbox"> Context Bundle 不重複 YAML frontmatter 已有欄位</li>
</ul>
<h3 id="token-節省">Token 節省</h3>
<ul>
<li><input disabled="" type="checkbox"> 無多餘客套與鋪陳</li>
<li><input disabled="" type="checkbox"> 重複句型已用表格取代</li>
<li><input disabled="" type="checkbox"> 規則以路徑引用取代全文貼入（如適用）</li>
<li><input disabled="" type="checkbox"> 邏輯關係用符號（<code>AND</code> / <code>→</code> / <code>;</code>）取代連接詞（如適用）</li>
<li><input disabled="" type="checkbox"> 通用知識（Markdown / JSON）用名稱指定，不枚舉語法</li>
</ul>
<hr>
<h2 id="多輪-re-read-passmulti-pass-refinement">多輪 Re-read Pass（multi-pass refinement）</h2>
<p>寫完上方自檢還不是 done — 自檢是「同 frame 的最後一掃」、不是 multi-pass。Multi-pass 要求每輪用<strong>不同 frame</strong> catch 不同層的錯（<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 — 不是所有錯誤都該被攔截。">literal-interception-vs-behavioral-refinement</a> / <a href="/blog/report/writing-multi-pass-review/" data-link-title="Writing 的 multi-pass review：N 輪 review、每輪換 frame" data-link-desc="寫文章 / 註解 / 文件 / prompt 的「寫」不是單次動作 — 是 N 輪 review。第 1 輪生成、第 2 輪對意圖（#67）、第 3 輪檢查機會成本語氣、第 4 輪 grep-ability、第 5 輪反例 / 邊界。每輪不同 frame、單輪寫不出全部維度。本卡是 #82 在「寫」這個 output 動作的具體實例。">writing-multi-pass-review</a>）。</p>
<p>Prompt 用的核心三輪 + 兩輪 prompt 專屬：</p>
<table>
  <thead>
      <tr>
          <th>輪</th>
          <th>Frame</th>
          <th>Prompt 專用 checklist</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>生成</td>
          <td>寫完任務 / 輸入 / 約束 / 輸出 / 驗收骨架、預期文字粗糙</td>
      </tr>
      <tr>
          <td>2</td>
          <td>對意圖（<a href="/blog/report/ease-of-writing-vs-intent-alignment/" data-link-title="寫作便利度跟意圖對齊反相關" data-link-desc="寫程式時最容易寫出的版本、通常是離意圖最遠的版本。便利度建立在「現有上下文 / 已 materialize 資料 / 已存在 API」上、而意圖對齊需要找到正確的層、處理上游、跨抽象層 — 兩者方向相反。識別這個反相關 = 識別自己掉進「容易寫的陷阱」。">ease-of-writing-vs-intent-alignment</a>）</td>
          <td>自己讀一遍、能想像 LLM 會做出什麼？跟你想要的一致嗎？</td>
      </tr>
      <tr>
          <td>3</td>
          <td>機會成本語氣</td>
          <td>「必須」「不可」翻成具體條件「在 X 條件下做 Y、否則做 Z」</td>
      </tr>
      <tr>
          <td>4''</td>
          <td>模糊指令清查</td>
          <td>grep「對齊 / 靠近 / 適當 / 合理 / 隔離」這類詞、翻成具體數字 / 條件；可用「定義域 / 資料源型態 / 空狀態」三問把模糊詞轉成可驗收條件</td>
      </tr>
      <tr>
          <td>5''</td>
          <td>邊界 case 預期</td>
          <td>「邊界 case 預期行為」明示了嗎？空 input、超大 input、衝突指令各會怎樣？</td>
      </tr>
  </tbody>
</table>
<p>跳輪規則：</p>
<ul>
<li>One-shot quick prompt：跑輪 1-2</li>
<li>Production agent / 反覆執行：全跑、每輪都跑</li>
<li>給多個不同 LLM 用：跑輪 4&rsquo;&rsquo; 兩遍（不同 LLM 對模糊詞解讀差異大）</li>
</ul>
<hr>
<h2 id="反模式速查">反模式速查</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>症狀</th>
          <th>正確做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>多任務混合 prompt</td>
          <td>「幫我做 A 和 B 和 C」</td>
          <td>拆為獨立 prompt</td>
      </tr>
      <tr>
          <td>意圖埋在後段</td>
          <td>前三段背景鋪陳，任務在第四段</td>
          <td>第一句即任務</td>
      </tr>
      <tr>
          <td>無輸出格式</td>
          <td>「分析一下」</td>
          <td>指定結構（表格 / 列表 / JSON）</td>
      </tr>
      <tr>
          <td>無理由禁令</td>
          <td>「不要用 X」無理由</td>
          <td>補「因為 Y」</td>
      </tr>
      <tr>
          <td>模糊佔位符</td>
          <td><code>{x}</code>、<code>{var}</code></td>
          <td>snake_case 自說明名</td>
      </tr>
      <tr>
          <td>客套語氣</td>
          <td>「您好，希望您能幫我&hellip;」</td>
          <td>直接下指令</td>
      </tr>
      <tr>
          <td>重複句型</td>
          <td>「當 X 時 Y，當 X2 時 Y2&hellip;」</td>
          <td>表格</td>
      </tr>
      <tr>
          <td>全文貼入規則</td>
          <td>把 500 行規則複製進 prompt</td>
          <td>引用路徑</td>
      </tr>
      <tr>
          <td>欄位混合</td>
          <td>任務 + 約束 + 驗收擠一行</td>
          <td>分欄位列出</td>
      </tr>
      <tr>
          <td>無結構化標記</td>
          <td>純散文</td>
          <td>章節 / 列表 / 表格</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li>Anthropic 官方 Prompt Engineering 文件（platform.claude.com）</li>
<li>Zettelkasten 原子化原則（本 skill <code>writing-documents.md</code> 的原子化章節）</li>
<li>結構化 output（JSON Schema / XML tags）在 AI 工具呼叫的應用</li>
</ul>
]]></content:encoded></item></channel></rss>