<?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>Framework on Tarragon</title><link>https://tarrragon.github.io/blog/tags/framework/</link><description>Recent content in Framework on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 26 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/framework/index.xml" rel="self" type="application/rss+xml"/><item><title>升級的共通操作框架</title><link>https://tarrragon.github.io/blog/infra/upgrade/upgrade-framework/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/upgrade/upgrade-framework/</guid><description>&lt;p>環境與系統升級的核心約束是系統在升級過程中要持續服務客戶。這個約束排除了「關機 → 換版本 → 開機」的簡單路徑，取而代之的操作模式是四個階段：評估新舊版本的差異、在旁邊建一個新環境驗證、把流量分批切過去、確認沒問題後退役舊環境。這四個階段不管升級的對象是 runtime 版本、資料庫引擎、作業系統還是整個平台，框架相同，差異落在每個階段的具體操作與風險點。&lt;/p>
&lt;h2 id="phase-1差異評估">Phase 1：差異評估&lt;/h2>
&lt;p>差異評估的產出是一份 change manifest——列出所有已知的新舊差異、每項的風險等級、以及需要的應對措施。這份清單是後續所有階段的依據：平行環境要驗證清單上的每一項、切換策略要先處理高風險項、退役前要確認清單上的所有相容性問題都已解決。&lt;/p>
&lt;h3 id="差異的三個維度">差異的三個維度&lt;/h3>
&lt;p>第一個維度是目標本身的變化。版本升級要看 changelog、breaking changes list、deprecated features list。平台遷移要看兩個平台的功能差異（共享主機沒有的 cron 彈性、VPS 有的 SSH 存取）。資料庫升級要看 SQL 語法差異、預設行為變更（如 MySQL 8.0 的 &lt;code>caching_sha2_password&lt;/code> 預設認證方式）。&lt;/p>
&lt;p>第二個維度是依賴關係。升級 PHP 版本時，所有 Composer 套件都可能受影響；升級 MySQL 時，ORM 的 SQL 生成可能不相容；遷移平台時，原本靠主機面板設定的 cron job 要改用系統 crontab 或雲端排程。依賴關係沒列完整，平行環境的測試就會漏掉受影響的元件。&lt;/p>
&lt;p>第三個維度是過渡期的雙版本相容性。升級不是瞬間完成的——在切換的過程中，系統的某些部分跑新版本、某些部分跑舊版本。這段期間兩個版本必須能共存：資料庫的 schema 要同時相容新舊版本的程式碼、API 的回應格式要讓新舊版本的客戶端都能處理、session 格式要能跨版本延續。&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;th>範例&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>低&lt;/td>
 &lt;td>向後相容、不需改 code&lt;/td>
 &lt;td>平行環境驗證即可&lt;/td>
 &lt;td>PHP 8.x 的效能改善&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>中&lt;/td>
 &lt;td>需要改 code 但改動明確&lt;/td>
 &lt;td>先改 code、確認新舊版本都能跑&lt;/td>
 &lt;td>deprecated function 替換&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>高&lt;/td>
 &lt;td>行為變更、可能影響商業邏輯&lt;/td>
 &lt;td>需要完整的功能測試 + 人工驗證&lt;/td>
 &lt;td>浮點數精度變更、排序預設值變更&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>阻塞&lt;/td>
 &lt;td>無法在新版本運作、沒有替代方案&lt;/td>
 &lt;td>必須在升級前解決或決定放棄升級&lt;/td>
 &lt;td>依賴的套件不支援新版本&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每一項差異分級後，高風險和阻塞項決定升級的可行性與時程。阻塞項超過團隊能處理的量時，升級可能需要拆成多個階段（先升到中間版本、再升到目標版本）或延後。&lt;/p>
&lt;h3 id="時程與管理層報告">時程與管理層報告&lt;/h3>
&lt;p>差異評估的時程通常佔整個升級的 20-30%——看起來「還沒開始做」但這段時間的產出（change manifest）決定了後面所有階段的範圍。向管理層報告時用 change manifest 的風險分級表：「共 N 項差異，其中 X 項低風險、Y 項中風險、Z 項高風險、W 項阻塞。中高風險項的處理估計 M 天，阻塞項的替代方案評估需要額外 K 天。」&lt;/p>
&lt;h2 id="phase-2平行環境驗證">Phase 2：平行環境驗證&lt;/h2>
&lt;p>平行環境驗證的責任是用事實證明「新版本在跟 production 相同的條件下能正常運作」。它的產出是一份驗證報告——每一項 change manifest 上的差異都標上「已驗證通過 / 有問題待修 / 不影響」。沒有這份報告就切換，等於在賭新版本會正常。&lt;/p>
&lt;h3 id="建立平行環境">建立平行環境&lt;/h3>
&lt;p>平行環境跟 production 越相似，驗證結果越可信。理想狀態是完全複製 production 的架構（同規格、同設定、同網路拓撲），只差目標元件的版本不同。成本限制下的折衷是用縮小版（較小的 instance、較少的資料量），但關鍵設定（PHP 模組、MySQL 參數、安全設定）必須跟 production 一致。&lt;/p>
&lt;p>資料的處理要特別注意。用 production 的資料副本驗證最可靠（能觸發真實的邊界狀況），但如果資料含 PII，需要先脫敏處理。另一個選項是用 staging 環境的資料，但要確認 staging 的 schema 跟 production 一致——schema drift 會讓驗證結果失真。&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>部署到新環境、觀察 log&lt;/td>
 &lt;td>無 fatal error、所有服務啟動成功&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>同樣的 workload 打新舊環境&lt;/td>
 &lt;td>回應時間差異 &amp;lt; 10%（或可解釋）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>相容性問題&lt;/td>
 &lt;td>逐一驗證 change manifest 的中高風險項&lt;/td>
 &lt;td>每項有「通過」或「已修」的紀錄&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>外部整合&lt;/td>
 &lt;td>第三方 API callback、webhook、email&lt;/td>
 &lt;td>外部服務能正常與新環境互動&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="平行期的時間長度">平行期的時間長度&lt;/h3>
&lt;p>平行環境跑多久才能切換？取決於業務週期。如果系統有月結、季結的批次處理，平行環境至少要跑過一次完整週期。電商系統要跑過至少一個促銷活動。沒有明顯週期的系統，一到兩週的平行驗證通常足夠發現主要問題。&lt;/p>
&lt;h2 id="phase-3分批切換">Phase 3：分批切換&lt;/h2>
&lt;p>分批切換的核心原則是不一次切 100%——先把最低風險的流量導到新環境，觀察一段時間確認正常，再逐步增加比例。&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;th>回退速度&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>DNS 權重切換&lt;/td>
 &lt;td>有多組 server 的環境&lt;/td>
 &lt;td>Route 53 weighted routing 或類似機制，逐步調整新舊比例&lt;/td>
 &lt;td>分鐘級（改 DNS 權重）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Blue-green&lt;/td>
 &lt;td>有 load balancer 的環境&lt;/td>
 &lt;td>新舊環境各掛在不同 target group，LB 切換指向&lt;/td>
 &lt;td>秒級（切 target group）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Canary&lt;/td>
 &lt;td>容器化或 serverless 環境&lt;/td>
 &lt;td>新版本只接 5% → 20% → 50% → 100% 流量&lt;/td>
 &lt;td>秒級（調整 weight）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>維護窗口&lt;/td>
 &lt;td>共享主機（無 LB）&lt;/td>
 &lt;td>公告停機時間、切換、驗證、恢復服務&lt;/td>
 &lt;td>分鐘級（FTP 上傳舊版）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>共享主機通常只能用維護窗口策略——沒有 load balancer 做流量分配、沒有 DNS 權重可調。維護窗口的關鍵是時間規劃：備份（15 分鐘）→ 切換（30 分鐘）→ 驗證（30 分鐘）→ 恢復或回退（15 分鐘），在窗口內必須完成全部步驟，超時就回退。&lt;/p></description><content:encoded><![CDATA[<p>環境與系統升級的核心約束是系統在升級過程中要持續服務客戶。這個約束排除了「關機 → 換版本 → 開機」的簡單路徑，取而代之的操作模式是四個階段：評估新舊版本的差異、在旁邊建一個新環境驗證、把流量分批切過去、確認沒問題後退役舊環境。這四個階段不管升級的對象是 runtime 版本、資料庫引擎、作業系統還是整個平台，框架相同，差異落在每個階段的具體操作與風險點。</p>
<h2 id="phase-1差異評估">Phase 1：差異評估</h2>
<p>差異評估的產出是一份 change manifest——列出所有已知的新舊差異、每項的風險等級、以及需要的應對措施。這份清單是後續所有階段的依據：平行環境要驗證清單上的每一項、切換策略要先處理高風險項、退役前要確認清單上的所有相容性問題都已解決。</p>
<h3 id="差異的三個維度">差異的三個維度</h3>
<p>第一個維度是目標本身的變化。版本升級要看 changelog、breaking changes list、deprecated features list。平台遷移要看兩個平台的功能差異（共享主機沒有的 cron 彈性、VPS 有的 SSH 存取）。資料庫升級要看 SQL 語法差異、預設行為變更（如 MySQL 8.0 的 <code>caching_sha2_password</code> 預設認證方式）。</p>
<p>第二個維度是依賴關係。升級 PHP 版本時，所有 Composer 套件都可能受影響；升級 MySQL 時，ORM 的 SQL 生成可能不相容；遷移平台時，原本靠主機面板設定的 cron job 要改用系統 crontab 或雲端排程。依賴關係沒列完整，平行環境的測試就會漏掉受影響的元件。</p>
<p>第三個維度是過渡期的雙版本相容性。升級不是瞬間完成的——在切換的過程中，系統的某些部分跑新版本、某些部分跑舊版本。這段期間兩個版本必須能共存：資料庫的 schema 要同時相容新舊版本的程式碼、API 的回應格式要讓新舊版本的客戶端都能處理、session 格式要能跨版本延續。</p>
<h3 id="風險分級">風險分級</h3>
<table>
  <thead>
      <tr>
          <th>風險等級</th>
          <th>定義</th>
          <th>應對方式</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>低</td>
          <td>向後相容、不需改 code</td>
          <td>平行環境驗證即可</td>
          <td>PHP 8.x 的效能改善</td>
      </tr>
      <tr>
          <td>中</td>
          <td>需要改 code 但改動明確</td>
          <td>先改 code、確認新舊版本都能跑</td>
          <td>deprecated function 替換</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>每一項差異分級後，高風險和阻塞項決定升級的可行性與時程。阻塞項超過團隊能處理的量時，升級可能需要拆成多個階段（先升到中間版本、再升到目標版本）或延後。</p>
<h3 id="時程與管理層報告">時程與管理層報告</h3>
<p>差異評估的時程通常佔整個升級的 20-30%——看起來「還沒開始做」但這段時間的產出（change manifest）決定了後面所有階段的範圍。向管理層報告時用 change manifest 的風險分級表：「共 N 項差異，其中 X 項低風險、Y 項中風險、Z 項高風險、W 項阻塞。中高風險項的處理估計 M 天，阻塞項的替代方案評估需要額外 K 天。」</p>
<h2 id="phase-2平行環境驗證">Phase 2：平行環境驗證</h2>
<p>平行環境驗證的責任是用事實證明「新版本在跟 production 相同的條件下能正常運作」。它的產出是一份驗證報告——每一項 change manifest 上的差異都標上「已驗證通過 / 有問題待修 / 不影響」。沒有這份報告就切換，等於在賭新版本會正常。</p>
<h3 id="建立平行環境">建立平行環境</h3>
<p>平行環境跟 production 越相似，驗證結果越可信。理想狀態是完全複製 production 的架構（同規格、同設定、同網路拓撲），只差目標元件的版本不同。成本限制下的折衷是用縮小版（較小的 instance、較少的資料量），但關鍵設定（PHP 模組、MySQL 參數、安全設定）必須跟 production 一致。</p>
<p>資料的處理要特別注意。用 production 的資料副本驗證最可靠（能觸發真實的邊界狀況），但如果資料含 PII，需要先脫敏處理。另一個選項是用 staging 環境的資料，但要確認 staging 的 schema 跟 production 一致——schema drift 會讓驗證結果失真。</p>
<h3 id="驗證清單">驗證清單</h3>
<table>
  <thead>
      <tr>
          <th>驗證項目</th>
          <th>方法</th>
          <th>通過標準</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>應用程式啟動</td>
          <td>部署到新環境、觀察 log</td>
          <td>無 fatal error、所有服務啟動成功</td>
      </tr>
      <tr>
          <td>自動化測試</td>
          <td>跑完整測試套件</td>
          <td>通過率跟舊環境一致</td>
      </tr>
      <tr>
          <td>關鍵業務流程</td>
          <td>人工操作核心流程（登入、下單、金流）</td>
          <td>每個步驟的結果正確</td>
      </tr>
      <tr>
          <td>效能比對</td>
          <td>同樣的 workload 打新舊環境</td>
          <td>回應時間差異 &lt; 10%（或可解釋）</td>
      </tr>
      <tr>
          <td>相容性問題</td>
          <td>逐一驗證 change manifest 的中高風險項</td>
          <td>每項有「通過」或「已修」的紀錄</td>
      </tr>
      <tr>
          <td>外部整合</td>
          <td>第三方 API callback、webhook、email</td>
          <td>外部服務能正常與新環境互動</td>
      </tr>
  </tbody>
</table>
<h3 id="平行期的時間長度">平行期的時間長度</h3>
<p>平行環境跑多久才能切換？取決於業務週期。如果系統有月結、季結的批次處理，平行環境至少要跑過一次完整週期。電商系統要跑過至少一個促銷活動。沒有明顯週期的系統，一到兩週的平行驗證通常足夠發現主要問題。</p>
<h2 id="phase-3分批切換">Phase 3：分批切換</h2>
<p>分批切換的核心原則是不一次切 100%——先把最低風險的流量導到新環境，觀察一段時間確認正常，再逐步增加比例。</p>
<h3 id="切換策略">切換策略</h3>
<table>
  <thead>
      <tr>
          <th>策略</th>
          <th>適用環境</th>
          <th>操作方式</th>
          <th>回退速度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DNS 權重切換</td>
          <td>有多組 server 的環境</td>
          <td>Route 53 weighted routing 或類似機制，逐步調整新舊比例</td>
          <td>分鐘級（改 DNS 權重）</td>
      </tr>
      <tr>
          <td>Blue-green</td>
          <td>有 load balancer 的環境</td>
          <td>新舊環境各掛在不同 target group，LB 切換指向</td>
          <td>秒級（切 target group）</td>
      </tr>
      <tr>
          <td>Canary</td>
          <td>容器化或 serverless 環境</td>
          <td>新版本只接 5% → 20% → 50% → 100% 流量</td>
          <td>秒級（調整 weight）</td>
      </tr>
      <tr>
          <td>維護窗口</td>
          <td>共享主機（無 LB）</td>
          <td>公告停機時間、切換、驗證、恢復服務</td>
          <td>分鐘級（FTP 上傳舊版）</td>
      </tr>
  </tbody>
</table>
<p>共享主機通常只能用維護窗口策略——沒有 load balancer 做流量分配、沒有 DNS 權重可調。維護窗口的關鍵是時間規劃：備份（15 分鐘）→ 切換（30 分鐘）→ 驗證（30 分鐘）→ 恢復或回退（15 分鐘），在窗口內必須完成全部步驟，超時就回退。</p>
<h3 id="切換期間的監控">切換期間的監控</h3>
<p>切換開始後要密切觀察的指標：</p>
<ul>
<li><strong>錯誤率</strong>：5xx / 4xx 比例相對於切換前的基線</li>
<li><strong>回應時間</strong>：p50 和 p99 相對於基線</li>
<li><strong>業務指標</strong>：轉換率、訂單數、付款成功率（如果適用）</li>
<li><strong>外部整合</strong>：第三方 callback 是否正常</li>
</ul>
<h3 id="回退觸發條件">回退觸發條件</h3>
<p>在切換前就定義好回退條件，避免事故發生時還要開會決定要不要退：</p>
<ul>
<li>錯誤率超過基線的 2 倍持續 5 分鐘 → 回退</li>
<li>核心業務流程失敗（登入、結帳、金流） → 立刻回退</li>
<li>回應時間超過基線的 3 倍持續 10 分鐘 → 回退</li>
</ul>
<p>回退不是失敗——它是風險控制機制的正常運作。回退後排查問題、修正、重新走 Phase 2 驗證、再嘗試切換。</p>
<h3 id="切換的通知">切換的通知</h3>
<table>
  <thead>
      <tr>
          <th>對象</th>
          <th>通知時機</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>內部團隊</td>
          <td>切換前 24 小時 + 切換開始時</td>
          <td>切換時間、影響範圍、回退計畫</td>
      </tr>
      <tr>
          <td>客戶（如有 SLA）</td>
          <td>切換前 1 週</td>
          <td>預計維護窗口、預期影響</td>
      </tr>
      <tr>
          <td>外部 vendor</td>
          <td>切換前 1 週</td>
          <td>endpoint 變更（如有）、IP 變更（如有）</td>
      </tr>
  </tbody>
</table>
<h2 id="phase-4退役舊環境">Phase 4：退役舊環境</h2>
<p>切換完成後不要立刻刪掉舊環境——保留 1-2 週的冷備。這段時間處理長尾問題：DNS 快取還沒更新的客戶端、排程任務還指向舊 endpoint 的外部系統、舊環境上可能還有未遷移的資料。</p>
<h3 id="退役前的檢查">退役前的檢查</h3>
<ul>
<li>舊環境的存取 log 是否歸零？（有流量代表還有東西指向它）</li>
<li>所有 cron job 是否都已在新環境運行？</li>
<li>外部系統的 webhook / callback URL 是否都已更新？</li>
<li>舊環境上有沒有需要歸檔的資料？（log、上傳檔案、備份快照）</li>
</ul>
<h3 id="退役步驟">退役步驟</h3>
<ol>
<li>停止舊環境的應用服務（但不刪除）</li>
<li>觀察 1 週——如果有問題可以快速重啟</li>
<li>匯出需要保留的資料（log、uploaded files）</li>
<li>刪除舊環境的運算資源（VM、容器）</li>
<li>保留舊環境的最後一份備份 30 天，作為最後的保險</li>
<li>清理舊環境的 DNS 記錄、SSL 憑證、IAM 角色</li>
</ol>
<h2 id="貫穿全程的升級紀律">貫穿全程的升級紀律</h2>
<h3 id="一次只升一個東西">一次只升一個東西</h3>
<p>同時升級 PHP 版本 + 遷移到新主機 + 重構資料庫 schema，出問題時無法判斷是哪個變更造成的。每次升級只改一個主要元件，穩定後再升下一個。如果業務壓力要求一次完成，至少在 Phase 2 的驗證環境裡逐一引入、逐一確認。</p>
<h3 id="每個階段轉換前備份">每個階段轉換前備份</h3>
<p>Phase 1 結束前備份 production 現況、Phase 3 切換前備份、Phase 4 退役前備份。三份備份各自獨立、各自有還原驗證。備份不只是「做了」——要實際測試過還原，確認備份的完整性。</p>
<h3 id="記錄每一步">記錄每一步</h3>
<p>每個升級操作記錄在 repo 的 changelog 裡：什麼時間、誰做的、改了什麼、觀察到什麼結果。升級出問題時，changelog 是回溯「上一步做了什麼」的唯一依據。</p>
<h3 id="在平行階段就練習回退">在平行階段就練習回退</h3>
<p>不要等到 Phase 3 切換時才第一次嘗試回退。在 Phase 2 的平行環境裡，刻意從新版本切回舊版本一次，確認回退路徑能走通、回退後服務能正常恢復。回退的演練跟升級的驗證同等重要。</p>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/infra/takeover/" data-link-title="接手維運：別人建的環境怎麼接管" data-link-desc="接手前人的專案時，怎麼在不搞壞東西的前提下盤點現況、建立維運能力、逐步正規化 — 從無 SSH 的 FTP 環境到有半套 IaC 的雲端環境都適用">接手維運</a>：接手後穩定維運的下一步常是升級</li>
<li>→ <a href="/blog/infra/05-core-services/" data-link-title="模組五：核心服務上 IaC" data-link-desc="資料庫、運算、儲存、load balancer 怎麼寫進基礎設施程式碼，以及上線順序">模組五：核心服務上 IaC</a>：stateful 資源（RDS、S3）的升級涉及特殊的備份與切換策略</li>
<li>→ <a href="/blog/infra/08-governance-habits/" data-link-title="模組八：治理好習慣 — 規模長大後不失控的最小節奏" data-link-desc="tagging 規範、secrets 不進 code、成本可見性、最小可行節奏，規模長大後不失控">模組八：治理好習慣</a>：升級期間的變更紀錄對齊治理紀律</li>
<li>→ <a href="/blog/infra/07-infra-as-pr/" data-link-title="模組七：infra 走 PR 流程與自動化護欄" data-link-desc="infra 變更走 PR → plan → review diff → 合併 → apply，配 fmt / validate / tflint / checkov / tfsec 與 Atlantis 自動化，讓基礎設施可審查、可回溯、可交接">模組七：infra 走 PR 流程</a>：升級涉及的 IaC 變更走 PR review</li>
</ul>
]]></content:encoded></item><item><title>媒介—讀者—目的矩陣</title><link>https://tarrragon.github.io/blog/business/reading-frameworks/reader-purpose-matrix/</link><pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/business/reading-frameworks/reader-purpose-matrix/</guid><description>&lt;p>媒介—讀者—目的矩陣的核心責任是把「眼前這篇文章是給誰看的」轉成一個可操作判斷。商業內容的類型決定它的密度、語氣、深度與該被怎麼讀；同一個事件（例如某家 AI 公司的併購案）在不同媒介上會被寫成完全不同的內容。&lt;/p>
&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;/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>Bloomberg / 彭博&lt;/td>
 &lt;td>機構投資人&lt;/td>
 &lt;td>看數字做交易&lt;/td>
 &lt;td>高密度數據、即時、不解釋背景&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>深度部落格 / 電子報&lt;/td>
 &lt;td>VC、創辦人、策略人&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>MBA 教材&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;h2 id="怎麼用這張表">怎麼用這張表&lt;/h2>
&lt;p>讀到一篇文章時依下面三步定位：&lt;/p>
&lt;p>第一步：辨識媒介。在哪個平台？電子報、部落格、新聞媒體、學術出版？對應到表中的一列。&lt;/p>
&lt;p>第二步：辨識預設讀者。文章開頭幾段假設讀者知道什麼？如果開頭就用 &lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/cogs/" data-link-title="COGS" data-link-desc="說明銷售成本與其對毛利的影響">COGS&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/cac/" data-link-title="CAC" data-link-desc="說明獲客成本及其對商業模式可行性的決定作用">CAC&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/ltv/" data-link-title="LTV" data-link-desc="說明客戶終身價值與其在估值中的作用">LTV&lt;/a> 而不解釋，預設讀者是 VC / 創辦人；如果開頭解釋每個術語，預設讀者是入門讀者。&lt;/p>
&lt;p>第三步：辨識寫作目的。作者是要「告訴你發生了什麼」「給你交易訊號」「建立分析框架」還是「安撫某群人的焦慮」？目的決定該怎麼用文章的結論。&lt;/p>
&lt;h2 id="常見誤讀">常見誤讀&lt;/h2>
&lt;p>把深度部落格當投資建議是最常見的誤讀。深度部落格寫「&lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/valuation-compression/" data-link-title="Valuation Compression" data-link-desc="說明估值壓縮如何影響新創生存">估值會被壓縮&lt;/a>」是在建立分析框架，不是叫你賣股票；把它當交易訊號去做決策會出大事。&lt;/p>
&lt;p>把產業內幕當大眾財經也常見。技術圈的「某家公司賣掉了」對工程師是賽道訊號，對大眾財經讀者沒太大意義；如果把這類文章轉給不懂技術的朋友看，他們會抓不到重點。&lt;/p>
&lt;p>把大眾財經當深度分析則相反—大眾財經為了易懂會犧牲精確度，用它的結論去做策略判斷會踩到簡化過的論證。&lt;/p>
&lt;h2 id="判讀訊號">判讀訊號&lt;/h2>
&lt;p>文章的術語密度、論證鏈長度、結論是否帶條件，三者能反映文章類型。深度部落格術語密度高、論證鏈長、結論帶條件；大眾財經三者都低；學術期刊三者都更高且引用嚴謹。把這三個訊號當定位錨點，能快速識別文章類型。&lt;/p>
&lt;h2 id="跟其他框架的關係">跟其他框架的關係&lt;/h2>
&lt;p>這個矩陣處理的是「文章類型定位」。它不處理「文章內容是否正確」（那要靠&lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/" data-link-title="商業概念知識卡片" data-link-desc="用原子化卡片整理商業模式、單位經濟、進入市場、競爭護城河、市場動態、資本估值與執行知識的術語">領域知識卡片&lt;/a> 跟外部查證），也不處理「作者立場是否中立」（那需要立場揭露檢查）。三者組合起來才完整—類型定位告訴你該怎麼讀、領域知識告訴你內容是不是對、立場揭露告訴你該打多少折扣。&lt;/p></description><content:encoded><![CDATA[<p>媒介—讀者—目的矩陣的核心責任是把「眼前這篇文章是給誰看的」轉成一個可操作判斷。商業內容的類型決定它的密度、語氣、深度與該被怎麼讀；同一個事件（例如某家 AI 公司的併購案）在不同媒介上會被寫成完全不同的內容。</p>
<h2 id="矩陣本體">矩陣本體</h2>
<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>Bloomberg / 彭博</td>
          <td>機構投資人</td>
          <td>看數字做交易</td>
          <td>高密度數據、即時、不解釋背景</td>
      </tr>
      <tr>
          <td>深度部落格 / 電子報</td>
          <td>VC、創辦人、策略人</td>
          <td>建立分析框架</td>
          <td>高術語密度、論證導向、結論常是「該怎麼看」</td>
      </tr>
      <tr>
          <td>學術期刊</td>
          <td>學者</td>
          <td>累積知識</td>
          <td>嚴謹方法、引用密集、結論限定條件多</td>
      </tr>
      <tr>
          <td>MBA 教材</td>
          <td>學生、經理人</td>
          <td>學通用框架</td>
          <td>中等密度、框架導向、案例搭配</td>
      </tr>
      <tr>
          <td>產業內幕（技術圈）</td>
          <td>領域工程師、技術主管</td>
          <td>安撫焦慮、給方向感</td>
          <td>帶感情、有梗、結論常是「我們做的事很重要」</td>
      </tr>
  </tbody>
</table>
<h2 id="怎麼用這張表">怎麼用這張表</h2>
<p>讀到一篇文章時依下面三步定位：</p>
<p>第一步：辨識媒介。在哪個平台？電子報、部落格、新聞媒體、學術出版？對應到表中的一列。</p>
<p>第二步：辨識預設讀者。文章開頭幾段假設讀者知道什麼？如果開頭就用 <a href="/blog/business/knowledge-cards/cogs/" data-link-title="COGS" data-link-desc="說明銷售成本與其對毛利的影響">COGS</a>、<a href="/blog/business/knowledge-cards/cac/" data-link-title="CAC" data-link-desc="說明獲客成本及其對商業模式可行性的決定作用">CAC</a>、<a href="/blog/business/knowledge-cards/ltv/" data-link-title="LTV" data-link-desc="說明客戶終身價值與其在估值中的作用">LTV</a> 而不解釋，預設讀者是 VC / 創辦人；如果開頭解釋每個術語，預設讀者是入門讀者。</p>
<p>第三步：辨識寫作目的。作者是要「告訴你發生了什麼」「給你交易訊號」「建立分析框架」還是「安撫某群人的焦慮」？目的決定該怎麼用文章的結論。</p>
<h2 id="常見誤讀">常見誤讀</h2>
<p>把深度部落格當投資建議是最常見的誤讀。深度部落格寫「<a href="/blog/business/knowledge-cards/valuation-compression/" data-link-title="Valuation Compression" data-link-desc="說明估值壓縮如何影響新創生存">估值會被壓縮</a>」是在建立分析框架，不是叫你賣股票；把它當交易訊號去做決策會出大事。</p>
<p>把產業內幕當大眾財經也常見。技術圈的「某家公司賣掉了」對工程師是賽道訊號，對大眾財經讀者沒太大意義；如果把這類文章轉給不懂技術的朋友看，他們會抓不到重點。</p>
<p>把大眾財經當深度分析則相反—大眾財經為了易懂會犧牲精確度，用它的結論去做策略判斷會踩到簡化過的論證。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<p>文章的術語密度、論證鏈長度、結論是否帶條件，三者能反映文章類型。深度部落格術語密度高、論證鏈長、結論帶條件；大眾財經三者都低；學術期刊三者都更高且引用嚴謹。把這三個訊號當定位錨點，能快速識別文章類型。</p>
<h2 id="跟其他框架的關係">跟其他框架的關係</h2>
<p>這個矩陣處理的是「文章類型定位」。它不處理「文章內容是否正確」（那要靠<a href="/blog/business/knowledge-cards/" data-link-title="商業概念知識卡片" data-link-desc="用原子化卡片整理商業模式、單位經濟、進入市場、競爭護城河、市場動態、資本估值與執行知識的術語">領域知識卡片</a> 跟外部查證），也不處理「作者立場是否中立」（那需要立場揭露檢查）。三者組合起來才完整—類型定位告訴你該怎麼讀、領域知識告訴你內容是不是對、立場揭露告訴你該打多少折扣。</p>
]]></content:encoded></item><item><title>降一級寫法：用矩陣框架讓技術讀者讀懂商業分析</title><link>https://tarrragon.github.io/blog/business/reading-frameworks/writing-down-a-level/</link><pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/business/reading-frameworks/writing-down-a-level/</guid><description>&lt;p>降一級寫法的核心目的是讓目標讀者比文章原本預設讀者低一級的人也能讀懂。寫商業 case-analyses 時、寫作者常不自覺把目標讀者預設成「跟自己同行的 VC / 創辦人 / 策略人」（&lt;a href="https://tarrragon.github.io/blog/business/reading-frameworks/reader-purpose-matrix/" data-link-title="媒介—讀者—目的矩陣" data-link-desc="用媒介、讀者、目的三軸定位一篇商業分析的類型，避免把策略分析誤讀成投資建議或把產業內幕誤讀成大眾財經">矩陣&lt;/a> 的「深度部落格」層）、但實際讀者是「工程背景、想理解商業分析」（接近「MBA 教材」層）。一級的落差不大、但累積起來會讓讀者在每段都需要回去查術語跟拆因果鏈、閱讀成本爆增。&lt;/p>
&lt;h2 id="為什麼會寫超過目標讀者一級">為什麼會寫超過目標讀者一級&lt;/h2>
&lt;p>寫商業分析的常見陷阱是把「自己熟悉的術語密度」當預設。寫作者跟 VC / 創辦人對 &lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/plg/" data-link-title="PLG" data-link-desc="說明產品自助成長模式與其經濟前提">PLG&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/cac/" data-link-title="CAC" data-link-desc="說明獲客成本及其對商業模式可行性的決定作用">CAC&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/unit-economics/" data-link-title="Unit Economics" data-link-desc="說明單位經濟模型與其判斷一家公司是否賺錢的責任">單位經濟&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/gross-margin/" data-link-title="Gross Margin" data-link-desc="說明毛利率與其對商業模式可行性的決定作用">毛利&lt;/a> 是日常詞彙、一句話三個術語也通順；但對技術背景讀者、這些術語都需要查、一句話三個術語就成了「停下來查三次」。&lt;/p>
&lt;p>實際 case：&lt;a href="https://tarrragon.github.io/blog/business/case-analyses/claude-for-legal/" data-link-title="Claude for Legal 之後：應用層、新創、知識工作者的三層擠壓" data-link-desc="用 WRAP 框架拆解基礎模型供應商進入垂直市場觸發的三層結構轉變：應用層 SaaS 毛利擠壓、新創淘汰、知識工作者判斷賭注放大">Claude for Legal 之後&lt;/a> 第一層擠壓段原本寫成：&lt;/p>
&lt;blockquote>
&lt;p>毛利下降是讓 P&amp;amp;L 跑不過去的差距。PLG 的數學算不過來、要轉成 Sales-led 或 FDE、但這又拉高 CAC。兩頭夾擊—單位經濟受傷、估值倍數被壓。&lt;/p>&lt;/blockquote>
&lt;p>3 句話塞了 6 個術語（毛利、P&amp;amp;L、PLG、Sales-led、FDE、CAC、單位經濟、估值倍數）。對 VC / 創辦人來說 3 秒讀完；對工程背景讀者來說每句都要停下來解碼、整段讀完得花 2-3 分鐘。&lt;/p>
&lt;h2 id="降一級的四個策略">降一級的四個策略&lt;/h2>
&lt;h3 id="策略一拆因果鏈每個邏輯-step-獨立成句">策略一：拆因果鏈、每個邏輯 step 獨立成句&lt;/h3>
&lt;p>原版把「毛利下降 → PLG 數學算不過來 → 要轉 Sales-led 或 FDE → CAC 拉高 → 兩頭夾擊 → 單位經濟受傷 → 估值倍數被壓」這個 7 步推論壓進 3 句話。每步邏輯跳躍對熟悉領域的人是省力、對讀者是要在腦中補完跳過的步驟。&lt;/p>
&lt;p>降一級的做法是把每個邏輯 step 獨立成段、用「第一 / 第二 / 第三」結構讓讀者跟得上：&lt;/p>
&lt;ul>
&lt;li>第一、賺到的錢不夠付業務跟行銷（毛利下降的直接後果）&lt;/li>
&lt;li>第二、免費試用變成燒錢（為什麼 PLG 數學算不過來）&lt;/li>
&lt;li>第三、被迫轉成更貴的銷售模式（為什麼要轉 Sales-led / FDE）&lt;/li>
&lt;/ul>
&lt;p>每段獨立、讀者一次處理一個概念、不用在腦中疊三個跳躍。&lt;/p>
&lt;h3 id="策略二術語首次出現時-unpack">策略二：術語首次出現時 unpack&lt;/h3>
&lt;p>原版的「&lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/plg/" data-link-title="PLG" data-link-desc="說明產品自助成長模式與其經濟前提">PLG&lt;/a>」連結到卡片、技術上沒問題、但讀者要中斷閱讀去點連結看卡片才能繼續。&lt;/p>
&lt;p>降一級的做法是首次出現時直接在括號裡解釋一句：&lt;/p>
&lt;blockquote>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/plg/" data-link-title="PLG" data-link-desc="說明產品自助成長模式與其經濟前提">PLG&lt;/a>（Product-Led Growth、靠產品自己吸引用戶上來、不靠業務推銷）&lt;/p>&lt;/blockquote>
&lt;p>讀者讀到這裡不用點開連結、括號裡的一句話就夠跟上後續論證。卡片連結保留、是給想深入的讀者用、不是預設動作。&lt;/p>
&lt;h3 id="策略三加具體算式--數字">策略三：加具體算式 / 數字&lt;/h3>
&lt;p>原版的「毛利下降」「兩頭夾擊」「估值倍數被壓」都是抽象描述。降一級的做法是配具體數字：&lt;/p>
&lt;ul>
&lt;li>毛利下降 → 「從 70-80% 掉到 50% 出頭、30 個百分點差距」&lt;/li>
&lt;li>兩頭夾擊 → 「收入端毛利從 70% 掉到 50%、支出端 CAC 從幾十美元跳到幾千甚至幾萬美元」&lt;/li>
&lt;li>估值倍數被壓 → 沒有直接數字、但前後的因果鏈讓讀者能自己推導&lt;/li>
&lt;/ul>
&lt;p>抽象描述對熟悉領域的人是「我知道你在說什麼」、對讀者是「我不知道這個尺度多大」。具體數字提供尺度感。&lt;/p>
&lt;h3 id="策略四避免術語連發術語之間用白話橋連接">策略四：避免術語連發、術語之間用「白話橋」連接&lt;/h3>
&lt;p>原版「PLG 的數學算不過來、要轉成 Sales-led 或 FDE、但這又拉高 CAC」是 4 個術語連發、白話讀者要在腦中組裝因果。&lt;/p>
&lt;p>降一級的做法是用白話句連接術語：&lt;/p>
&lt;blockquote>
&lt;p>PLG 不能用、改回業務面對面賣（Sales-led）、或乾脆派工程師駐點客戶辦公室（FDE、Forward Deployed Engineer）&lt;/p>&lt;/blockquote>
&lt;p>「改回業務面對面賣」「乾脆派工程師駐點客戶辦公室」這兩句白話橋讓讀者看到從 PLG 轉到 Sales-led 或 FDE 的具體畫面、不是術語對術語的跳躍。&lt;/p>
&lt;h2 id="對照示範">對照示範&lt;/h2>
&lt;p>同一段內容、用兩個 register 寫：&lt;/p>
&lt;p>&lt;strong>原版（深度部落格 / VC、創辦人）&lt;/strong>：&lt;/p>
&lt;blockquote>
&lt;p>毛利下降是讓 &lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/pnl/" data-link-title="P&amp;amp;L" data-link-desc="說明損益表的結構與商業判讀作用">P&amp;amp;L&lt;/a> 跑不過去的差距。&lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/plg/" data-link-title="PLG" data-link-desc="說明產品自助成長模式與其經濟前提">PLG&lt;/a> 的數學算不過來、要轉成 Sales-led 或 &lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/fde/" data-link-title="FDE" data-link-desc="說明前線部署工程師模式的成立條件">FDE&lt;/a>、但這又拉高 &lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/cac/" data-link-title="CAC" data-link-desc="說明獲客成本及其對商業模式可行性的決定作用">CAC&lt;/a>。兩頭夾擊—&lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/unit-economics/" data-link-title="Unit Economics" data-link-desc="說明單位經濟模型與其判斷一家公司是否賺錢的責任">單位經濟&lt;/a> 受傷、&lt;a href="https://tarrragon.github.io/blog/business/knowledge-cards/valuation/" data-link-title="Valuation" data-link-desc="說明估值的構成與商業判讀作用">估值&lt;/a> 倍數被壓。&lt;/p>&lt;/blockquote>
&lt;p>3 句、約 100 字。&lt;/p>
&lt;p>&lt;strong>降一級（MBA 教材 / 學生、經理人 / 工程背景讀者）&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>降一級寫法的核心目的是讓目標讀者比文章原本預設讀者低一級的人也能讀懂。寫商業 case-analyses 時、寫作者常不自覺把目標讀者預設成「跟自己同行的 VC / 創辦人 / 策略人」（<a href="/blog/business/reading-frameworks/reader-purpose-matrix/" data-link-title="媒介—讀者—目的矩陣" data-link-desc="用媒介、讀者、目的三軸定位一篇商業分析的類型，避免把策略分析誤讀成投資建議或把產業內幕誤讀成大眾財經">矩陣</a> 的「深度部落格」層）、但實際讀者是「工程背景、想理解商業分析」（接近「MBA 教材」層）。一級的落差不大、但累積起來會讓讀者在每段都需要回去查術語跟拆因果鏈、閱讀成本爆增。</p>
<h2 id="為什麼會寫超過目標讀者一級">為什麼會寫超過目標讀者一級</h2>
<p>寫商業分析的常見陷阱是把「自己熟悉的術語密度」當預設。寫作者跟 VC / 創辦人對 <a href="/blog/business/knowledge-cards/plg/" data-link-title="PLG" data-link-desc="說明產品自助成長模式與其經濟前提">PLG</a>、<a href="/blog/business/knowledge-cards/cac/" data-link-title="CAC" data-link-desc="說明獲客成本及其對商業模式可行性的決定作用">CAC</a>、<a href="/blog/business/knowledge-cards/unit-economics/" data-link-title="Unit Economics" data-link-desc="說明單位經濟模型與其判斷一家公司是否賺錢的責任">單位經濟</a>、<a href="/blog/business/knowledge-cards/gross-margin/" data-link-title="Gross Margin" data-link-desc="說明毛利率與其對商業模式可行性的決定作用">毛利</a> 是日常詞彙、一句話三個術語也通順；但對技術背景讀者、這些術語都需要查、一句話三個術語就成了「停下來查三次」。</p>
<p>實際 case：<a href="/blog/business/case-analyses/claude-for-legal/" data-link-title="Claude for Legal 之後：應用層、新創、知識工作者的三層擠壓" data-link-desc="用 WRAP 框架拆解基礎模型供應商進入垂直市場觸發的三層結構轉變：應用層 SaaS 毛利擠壓、新創淘汰、知識工作者判斷賭注放大">Claude for Legal 之後</a> 第一層擠壓段原本寫成：</p>
<blockquote>
<p>毛利下降是讓 P&amp;L 跑不過去的差距。PLG 的數學算不過來、要轉成 Sales-led 或 FDE、但這又拉高 CAC。兩頭夾擊—單位經濟受傷、估值倍數被壓。</p></blockquote>
<p>3 句話塞了 6 個術語（毛利、P&amp;L、PLG、Sales-led、FDE、CAC、單位經濟、估值倍數）。對 VC / 創辦人來說 3 秒讀完；對工程背景讀者來說每句都要停下來解碼、整段讀完得花 2-3 分鐘。</p>
<h2 id="降一級的四個策略">降一級的四個策略</h2>
<h3 id="策略一拆因果鏈每個邏輯-step-獨立成句">策略一：拆因果鏈、每個邏輯 step 獨立成句</h3>
<p>原版把「毛利下降 → PLG 數學算不過來 → 要轉 Sales-led 或 FDE → CAC 拉高 → 兩頭夾擊 → 單位經濟受傷 → 估值倍數被壓」這個 7 步推論壓進 3 句話。每步邏輯跳躍對熟悉領域的人是省力、對讀者是要在腦中補完跳過的步驟。</p>
<p>降一級的做法是把每個邏輯 step 獨立成段、用「第一 / 第二 / 第三」結構讓讀者跟得上：</p>
<ul>
<li>第一、賺到的錢不夠付業務跟行銷（毛利下降的直接後果）</li>
<li>第二、免費試用變成燒錢（為什麼 PLG 數學算不過來）</li>
<li>第三、被迫轉成更貴的銷售模式（為什麼要轉 Sales-led / FDE）</li>
</ul>
<p>每段獨立、讀者一次處理一個概念、不用在腦中疊三個跳躍。</p>
<h3 id="策略二術語首次出現時-unpack">策略二：術語首次出現時 unpack</h3>
<p>原版的「<a href="/blog/business/knowledge-cards/plg/" data-link-title="PLG" data-link-desc="說明產品自助成長模式與其經濟前提">PLG</a>」連結到卡片、技術上沒問題、但讀者要中斷閱讀去點連結看卡片才能繼續。</p>
<p>降一級的做法是首次出現時直接在括號裡解釋一句：</p>
<blockquote>
<p><a href="/blog/business/knowledge-cards/plg/" data-link-title="PLG" data-link-desc="說明產品自助成長模式與其經濟前提">PLG</a>（Product-Led Growth、靠產品自己吸引用戶上來、不靠業務推銷）</p></blockquote>
<p>讀者讀到這裡不用點開連結、括號裡的一句話就夠跟上後續論證。卡片連結保留、是給想深入的讀者用、不是預設動作。</p>
<h3 id="策略三加具體算式--數字">策略三：加具體算式 / 數字</h3>
<p>原版的「毛利下降」「兩頭夾擊」「估值倍數被壓」都是抽象描述。降一級的做法是配具體數字：</p>
<ul>
<li>毛利下降 → 「從 70-80% 掉到 50% 出頭、30 個百分點差距」</li>
<li>兩頭夾擊 → 「收入端毛利從 70% 掉到 50%、支出端 CAC 從幾十美元跳到幾千甚至幾萬美元」</li>
<li>估值倍數被壓 → 沒有直接數字、但前後的因果鏈讓讀者能自己推導</li>
</ul>
<p>抽象描述對熟悉領域的人是「我知道你在說什麼」、對讀者是「我不知道這個尺度多大」。具體數字提供尺度感。</p>
<h3 id="策略四避免術語連發術語之間用白話橋連接">策略四：避免術語連發、術語之間用「白話橋」連接</h3>
<p>原版「PLG 的數學算不過來、要轉成 Sales-led 或 FDE、但這又拉高 CAC」是 4 個術語連發、白話讀者要在腦中組裝因果。</p>
<p>降一級的做法是用白話句連接術語：</p>
<blockquote>
<p>PLG 不能用、改回業務面對面賣（Sales-led）、或乾脆派工程師駐點客戶辦公室（FDE、Forward Deployed Engineer）</p></blockquote>
<p>「改回業務面對面賣」「乾脆派工程師駐點客戶辦公室」這兩句白話橋讓讀者看到從 PLG 轉到 Sales-led 或 FDE 的具體畫面、不是術語對術語的跳躍。</p>
<h2 id="對照示範">對照示範</h2>
<p>同一段內容、用兩個 register 寫：</p>
<p><strong>原版（深度部落格 / VC、創辦人）</strong>：</p>
<blockquote>
<p>毛利下降是讓 <a href="/blog/business/knowledge-cards/pnl/" data-link-title="P&amp;L" data-link-desc="說明損益表的結構與商業判讀作用">P&amp;L</a> 跑不過去的差距。<a href="/blog/business/knowledge-cards/plg/" data-link-title="PLG" data-link-desc="說明產品自助成長模式與其經濟前提">PLG</a> 的數學算不過來、要轉成 Sales-led 或 <a href="/blog/business/knowledge-cards/fde/" data-link-title="FDE" data-link-desc="說明前線部署工程師模式的成立條件">FDE</a>、但這又拉高 <a href="/blog/business/knowledge-cards/cac/" data-link-title="CAC" data-link-desc="說明獲客成本及其對商業模式可行性的決定作用">CAC</a>。兩頭夾擊—<a href="/blog/business/knowledge-cards/unit-economics/" data-link-title="Unit Economics" data-link-desc="說明單位經濟模型與其判斷一家公司是否賺錢的責任">單位經濟</a> 受傷、<a href="/blog/business/knowledge-cards/valuation/" data-link-title="Valuation" data-link-desc="說明估值的構成與商業判讀作用">估值</a> 倍數被壓。</p></blockquote>
<p>3 句、約 100 字。</p>
<p><strong>降一級（MBA 教材 / 學生、經理人 / 工程背景讀者）</strong>：</p>
<blockquote>
<p>這個毛利下降會連動三件事。</p>
<p><strong>第一、賺到的錢不夠付業務跟行銷。</strong> 傳統 SaaS 賣 100 元、扣掉伺服器費用後剩 70-80 元（毛利 70-80%）、即使花 30% 在業務跟行銷也還能賺；AI 應用賣 100 元、要付給上游模型供應商的 token 費後只剩 50 元出頭（毛利 50%）、同樣花 30% 在業務跟行銷只剩 20% 利潤、<a href="/blog/business/knowledge-cards/pnl/" data-link-title="P&amp;L" data-link-desc="說明損益表的結構與商業判讀作用">損益表 P&amp;L</a>（公司一段期間內賺賠的財務報表）從正轉負。</p>
<p><strong>第二、免費試用變成燒錢。</strong> 傳統 SaaS 的「免費試用」幾乎零成本—多開帳號伺服器頂多多用一點；AI 應用的免費試用每次都在燒 GPU 算力、是真實的成本支出。<a href="/blog/business/knowledge-cards/plg/" data-link-title="PLG" data-link-desc="說明產品自助成長模式與其經濟前提">PLG</a>（Product-Led Growth、靠產品自己吸引用戶上來、不靠業務推銷）模式靠的就是「免費試用零成本」這個前提、毛利掉到 50% 時這套數學就跑不下去了。</p>
<p><strong>第三、被迫轉成更貴的銷售模式。</strong> PLG 不能用、改回業務面對面賣（Sales-led）、或乾脆派工程師駐點客戶辦公室（<a href="/blog/business/knowledge-cards/fde/" data-link-title="FDE" data-link-desc="說明前線部署工程師模式的成立條件">FDE</a>、Forward Deployed Engineer）、但這兩條路都讓 <a href="/blog/business/knowledge-cards/cac/" data-link-title="CAC" data-link-desc="說明獲客成本及其對商業模式可行性的決定作用">CAC</a>（Customer Acquisition Cost、獲取一個新客戶要花的所有成本）從 PLG 的幾十美元跳到 Sales-led 的幾千美元、再到 FDE 的幾萬甚至幾十萬美元。</p>
<p>收入端（毛利從 70% 掉到 50%）被壓縮、支出端（CAC 上升 100 倍）被拉高—兩頭夾擊讓 <a href="/blog/business/knowledge-cards/unit-economics/" data-link-title="Unit Economics" data-link-desc="說明單位經濟模型與其判斷一家公司是否賺錢的責任">單位經濟</a>（每個客戶能不能帶來足夠收入回本獲客成本）受傷。投資人計算 <a href="/blog/business/knowledge-cards/valuation/" data-link-title="Valuation" data-link-desc="說明估值的構成與商業判讀作用">估值</a> 倍數時看到這個結構性壓縮、給的估值就低、新創 <a href="/blog/business/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明燒錢速度及其對新創存活的決定作用">burn rate</a>（每月燒錢速度）變相加速、生存難度提高。</p></blockquote>
<p>5 段、約 500 字。長度增 5 倍、但讀者一次處理一個概念、不用中斷查術語。</p>
<h2 id="篇幅成本跟對齊讀者的取捨">篇幅成本跟對齊讀者的取捨</h2>
<p>降一級的代價是篇幅 3-5 倍。寫作者要判斷這個代價值不值：</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>第一次降級 unpack、後續直接用詞</td>
      </tr>
  </tbody>
</table>
<p>判別線：「我希望讀者直接吸收還是先學術語」。前者降級、後者保留密度。</p>
<h2 id="跨領域應用技術文章降級給商業讀者">跨領域應用：技術文章降級給商業讀者</h2>
<p>降一級寫法不只用在「商業降給工程師」這個方向、反方向也成立。寫技術文章給商業讀者看時、同樣的策略適用：</p>
<ul>
<li>拆因果鏈：把「Eventually Consistency 導致 stale read」拆成「資料更新後不會立刻所有人都看到、可能會看到舊版」</li>
<li>Unpack 術語：「Eventual Consistency（最終一致性、資料更新會慢慢傳到所有副本）」</li>
<li>加具體數字 / 例子：「副本之間最多差 100 毫秒、千分之一的請求會讀到舊版」</li>
<li>用白話橋連接術語：「資料庫一寫入、其他副本可能要 100 毫秒才同步過來、這段時間讀到的是舊版」</li>
</ul>
<p>矩陣框架的可遷移性：判別目標讀者層級 → 降一級寫法 → 對照原版檢查篇幅 vs 易讀性的取捨。任何跨領域知識傳達都適用。</p>
<h2 id="完稿時的降級檢查">完稿時的「降級檢查」</h2>
<p>寫完一段後跑：</p>
<ol>
<li>數一下這段塞了幾個術語（縮寫、行話、領域特定詞）</li>
<li>三個以上術語連發、考慮拆段或加白話橋</li>
<li>因果鏈跳躍超過 2 步、考慮用「第一 / 第二 / 第三」結構拆細</li>
<li>術語首次出現時、考慮在括號裡加一句解釋</li>
<li>抽象描述（「夾擊」「擠壓」「鬆動」）配具體數字或例子</li>
</ol>
<p>完稿後找一個目標讀者層級的人試讀、看哪段需要解釋、就是降級沒做夠的地方。</p>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="/blog/business/reading-frameworks/reader-purpose-matrix/" data-link-title="媒介—讀者—目的矩陣" data-link-desc="用媒介、讀者、目的三軸定位一篇商業分析的類型，避免把策略分析誤讀成投資建議或把產業內幕誤讀成大眾財經">媒介—讀者—目的矩陣</a> — 識別文章類型跟目標讀者</li>
<li><a href="/blog/business/case-analyses/claude-for-legal/" data-link-title="Claude for Legal 之後：應用層、新創、知識工作者的三層擠壓" data-link-desc="用 WRAP 框架拆解基礎模型供應商進入垂直市場觸發的三層結構轉變：應用層 SaaS 毛利擠壓、新創淘汰、知識工作者判斷賭注放大">Claude for Legal 之後</a> — 採用降一級寫法的 case-analyses 示範</li>
</ul>
]]></content:encoded></item><item><title>外部分析改寫的交付物是可遷移框架、不是風格轉換</title><link>https://tarrragon.github.io/blog/report/analysis-rewrite-must-deliver-transferable-framework/</link><pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/analysis-rewrite-must-deliver-transferable-framework/</guid><description>&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>把 WRAP process 改成教學章節&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;hr>
&lt;h2 id="情境">情境&lt;/h2>
&lt;p>&lt;code>content/business/&lt;/code> 的建立過程是一場從「外部分析文章」到「本站教學型商業知識」的轉換實驗。commit 演變顯示，早期版本雖然有 WRAP、知識卡與 case analyses，但仍多次被讀者 feedback 拉回核心問題：&lt;/p>
&lt;ul>
&lt;li>&lt;code>#140&lt;/code>：Widen Options 不能用稻草人修辭展示作者結論。&lt;/li>
&lt;li>&lt;code>#141&lt;/code>：WRAP 是內部工具，不能直接當文章章節。&lt;/li>
&lt;li>&lt;code>#142&lt;/code>：即使章節標題改了，正文仍要對齊標題承諾。&lt;/li>
&lt;li>&lt;code>writing-down-a-level&lt;/code>：目標讀者是工程背景讀者，不是原文的投資人或創辦人。&lt;/li>
&lt;/ul>
&lt;p>這些問題共同指向一件事：AI 轉換文章風格不夠。文章要從「原作者如何看這件事」轉成「本站讀者以後如何判讀同類事件」。&lt;/p>
&lt;hr>
&lt;h2 id="理想做法">理想做法&lt;/h2>
&lt;h3 id="第一步把任務定義成-frame-extraction">第一步：把任務定義成 frame extraction&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>這篇原文用什麼 frame 看事件？&lt;/td>
 &lt;td>辨識原作者判讀，不直接繼承&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>這個 frame 對本站讀者有沒有可遷移價值？&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>若事件只能產出「某公司發生某事」的摘要、沒有可遷移框架，就放在筆記，不硬寫成 case-analysis。&lt;/p>
&lt;h3 id="第二步正文結構服務可遷移框架">第二步：正文結構服務可遷移框架&lt;/h3>
&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;p>若某篇文章的標題承諾需要不同順序，可以調整；但最後仍要回答「這個事件教讀者下次看什麼」。&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;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>AI legal tools 會擠壓知識工作者&lt;/td>
 &lt;td>這個工具在擠壓應用層、新創、工作者哪一層？&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>FDE 是資料平台的新戰場&lt;/td>
 &lt;td>它是否鬆動 SaaS 的 distribution、workflow、data 三支柱？&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>收購代表基礎設施整併&lt;/td>
 &lt;td>這是純收購、賽道整併，還是算力廠商垂直整合？&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>判斷問題比結論更有價值，因為問題可以帶到下一個 case。&lt;/p>
&lt;h3 id="第四步用預警訊號保護框架">第四步：用預警訊號保護框架&lt;/h3>
&lt;p>可遷移框架需要知道何時失效。每篇 case-analysis 至少要有一段預警訊號，列出哪些觀察會推翻或削弱本文判讀。&lt;/p>
&lt;p>沒有預警訊號的框架只是一組漂亮分類。讀者需要知道「何時重新評估」，才會把框架當工具，而不是把它當口號。&lt;/p>
&lt;hr>
&lt;h2 id="沒這樣做的麻煩">沒這樣做的麻煩&lt;/h2>
&lt;h3 id="文章會變成原文摘要">文章會變成原文摘要&lt;/h3>
&lt;p>AI 很擅長把原文改成不同語氣，但摘要仍然沿著原文的 frame 走。讀者得到的是「另一個版本的原文」，不是本站累積出的知識單元。&lt;/p>
&lt;h3 id="事件評論不可重用">事件評論不可重用&lt;/h3>
&lt;p>若文章只回答「這件事代表什麼」，下次遇到不同公司、不同市場、不同產品時，讀者還是要從頭判斷。可遷移框架要回答「下次遇到相似事件，我要看哪些訊號」。&lt;/p>
&lt;h3 id="寫作規範會被誤解成表面風格">寫作規範會被誤解成表面風格&lt;/h3>
&lt;p>正向陳述、核心原則先行、商業邏輯先於 CASE 不是語氣規則；它們是把思考過程變成可重用知識的結構規則。若只做風格轉換，會通過部分表面檢查，但仍不符合知識庫目標。&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="../wrap-as-internal-tool-not-section-structure/">#141 WRAP 是寫作者的內部工具&lt;/a>&lt;/td>
 &lt;td>#141 處理 process 不能外露，本卡處理 process 之後要交付什麼&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="../article-body-must-align-with-title-commitment/">#142 文章主體要對齊標題承諾&lt;/a>&lt;/td>
 &lt;td>標題承諾應該指向可遷移框架；若標題只承諾事件評論，文章容易停在摘要&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="../external-analysis-source-layering/">#143 外部分析文章要先拆成事實、作者判讀、本文推導&lt;/a>&lt;/td>
 &lt;td>Source 分層是框架轉換的前置步驟；先知道哪些是原文觀點，才能抽出本站框架&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="../cross-domain-reader-level-alignment/">#144 跨領域分析要先定位讀者層級&lt;/a>&lt;/td>
 &lt;td>可遷移框架要用目標讀者能操作的語言表達，不能保留原文的高密度 shorthand&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="../cards-as-living-system-iteration/">#81 卡片系統的迭代浮現&lt;/a>&lt;/td>
 &lt;td>一篇 case-analysis 產生的框架若反覆出現，後續可升級成知識卡或 reading framework&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>Prompt 只說「改成我們的風格」&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>文章沒有預警訊號或 Tripwire&lt;/td>
 &lt;td>補失效條件，讓框架可被反證&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>文章大量沿用原文 frame&lt;/td>
 &lt;td>回到 source layering，區分原文判讀與本文推導&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>核心原則&lt;/strong>：外部分析改寫的交付物是讀者可遷移的判讀框架，把原文變順只是表層。沒有框架、預警與下一步路由，文章仍停在摘要層。&lt;/p></description><content:encoded><![CDATA[<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>把 WRAP process 改成教學章節</td>
          <td>可能仍偏離標題承諾</td>
      </tr>
      <tr>
          <td>框架轉換</td>
          <td>事件訊號、機制、風險、預警、遷移用法</td>
          <td>讀者能用來分析下一個事件</td>
      </tr>
  </tbody>
</table>
<p>判別問題：「讀者讀完後，是否多了一個可重用的判斷問題或檢查表？」沒有，就還只是改寫。</p>
<hr>
<h2 id="情境">情境</h2>
<p><code>content/business/</code> 的建立過程是一場從「外部分析文章」到「本站教學型商業知識」的轉換實驗。commit 演變顯示，早期版本雖然有 WRAP、知識卡與 case analyses，但仍多次被讀者 feedback 拉回核心問題：</p>
<ul>
<li><code>#140</code>：Widen Options 不能用稻草人修辭展示作者結論。</li>
<li><code>#141</code>：WRAP 是內部工具，不能直接當文章章節。</li>
<li><code>#142</code>：即使章節標題改了，正文仍要對齊標題承諾。</li>
<li><code>writing-down-a-level</code>：目標讀者是工程背景讀者，不是原文的投資人或創辦人。</li>
</ul>
<p>這些問題共同指向一件事：AI 轉換文章風格不夠。文章要從「原作者如何看這件事」轉成「本站讀者以後如何判讀同類事件」。</p>
<hr>
<h2 id="理想做法">理想做法</h2>
<h3 id="第一步把任務定義成-frame-extraction">第一步：把任務定義成 frame extraction</h3>
<p>輸入外部文章時，先問：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>作用</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>這篇原文用什麼 frame 看事件？</td>
          <td>辨識原作者判讀，不直接繼承</td>
      </tr>
      <tr>
          <td>這個 frame 對本站讀者有沒有可遷移價值？</td>
          <td>決定是否值得寫成文章</td>
      </tr>
      <tr>
          <td>若要遷移，讀者下次要看哪些訊號？</td>
          <td>把評論轉成判讀工具</td>
      </tr>
      <tr>
          <td>哪些術語需要卡片支撐？</td>
          <td>避免正文變成術語堆疊</td>
      </tr>
  </tbody>
</table>
<p>若事件只能產出「某公司發生某事」的摘要、沒有可遷移框架，就放在筆記，不硬寫成 case-analysis。</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></code></pre></div><p>這是責任順序，可依標題承諾調整。事件段讓讀者進場；機制段教為什麼；長期影響處理時間軸；預警訊號讓框架可被反證；可遷移框架讓讀者能帶走。</p>
<p>若某篇文章的標題承諾需要不同順序，可以調整；但最後仍要回答「這個事件教讀者下次看什麼」。</p>
<h3 id="第三步把原文觀點轉成判斷問題">第三步：把原文觀點轉成判斷問題</h3>
<p>風格轉換會把原文句子改得更順；框架轉換會把原文觀點改成讀者可問的問題。</p>
<table>
  <thead>
      <tr>
          <th>原文觀點型句子</th>
          <th>框架型問題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>AI legal tools 會擠壓知識工作者</td>
          <td>這個工具在擠壓應用層、新創、工作者哪一層？</td>
      </tr>
      <tr>
          <td>FDE 是資料平台的新戰場</td>
          <td>它是否鬆動 SaaS 的 distribution、workflow、data 三支柱？</td>
      </tr>
      <tr>
          <td>收購代表基礎設施整併</td>
          <td>這是純收購、賽道整併，還是算力廠商垂直整合？</td>
      </tr>
  </tbody>
</table>
<p>判斷問題比結論更有價值，因為問題可以帶到下一個 case。</p>
<h3 id="第四步用預警訊號保護框架">第四步：用預警訊號保護框架</h3>
<p>可遷移框架需要知道何時失效。每篇 case-analysis 至少要有一段預警訊號，列出哪些觀察會推翻或削弱本文判讀。</p>
<p>沒有預警訊號的框架只是一組漂亮分類。讀者需要知道「何時重新評估」，才會把框架當工具，而不是把它當口號。</p>
<hr>
<h2 id="沒這樣做的麻煩">沒這樣做的麻煩</h2>
<h3 id="文章會變成原文摘要">文章會變成原文摘要</h3>
<p>AI 很擅長把原文改成不同語氣，但摘要仍然沿著原文的 frame 走。讀者得到的是「另一個版本的原文」，不是本站累積出的知識單元。</p>
<h3 id="事件評論不可重用">事件評論不可重用</h3>
<p>若文章只回答「這件事代表什麼」，下次遇到不同公司、不同市場、不同產品時，讀者還是要從頭判斷。可遷移框架要回答「下次遇到相似事件，我要看哪些訊號」。</p>
<h3 id="寫作規範會被誤解成表面風格">寫作規範會被誤解成表面風格</h3>
<p>正向陳述、核心原則先行、商業邏輯先於 CASE 不是語氣規則；它們是把思考過程變成可重用知識的結構規則。若只做風格轉換，會通過部分表面檢查，但仍不符合知識庫目標。</p>
<hr>
<h2 id="跟其他抽象層原則的關係">跟其他抽象層原則的關係</h2>
<table>
  <thead>
      <tr>
          <th>原則</th>
          <th>跟本卡的關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="../wrap-as-internal-tool-not-section-structure/">#141 WRAP 是寫作者的內部工具</a></td>
          <td>#141 處理 process 不能外露，本卡處理 process 之後要交付什麼</td>
      </tr>
      <tr>
          <td><a href="../article-body-must-align-with-title-commitment/">#142 文章主體要對齊標題承諾</a></td>
          <td>標題承諾應該指向可遷移框架；若標題只承諾事件評論，文章容易停在摘要</td>
      </tr>
      <tr>
          <td><a href="../external-analysis-source-layering/">#143 外部分析文章要先拆成事實、作者判讀、本文推導</a></td>
          <td>Source 分層是框架轉換的前置步驟；先知道哪些是原文觀點，才能抽出本站框架</td>
      </tr>
      <tr>
          <td><a href="../cross-domain-reader-level-alignment/">#144 跨領域分析要先定位讀者層級</a></td>
          <td>可遷移框架要用目標讀者能操作的語言表達，不能保留原文的高密度 shorthand</td>
      </tr>
      <tr>
          <td><a href="../cards-as-living-system-iteration/">#81 卡片系統的迭代浮現</a></td>
          <td>一篇 case-analysis 產生的框架若反覆出現，後續可升級成知識卡或 reading framework</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Prompt 只說「改成我們的風格」</td>
          <td>改成「抽出可遷移判讀框架」</td>
      </tr>
      <tr>
          <td>文章結尾只有事件結論</td>
          <td>補「下次遇到同類事件要看什麼」</td>
      </tr>
      <tr>
          <td>讀者讀完只能說出某公司發生什麼</td>
          <td>補訊號、機制、風險、預警與路由</td>
      </tr>
      <tr>
          <td>文章沒有預警訊號或 Tripwire</td>
          <td>補失效條件，讓框架可被反證</td>
      </tr>
      <tr>
          <td>文章大量沿用原文 frame</td>
          <td>回到 source layering，區分原文判讀與本文推導</td>
      </tr>
  </tbody>
</table>
<p><strong>核心原則</strong>：外部分析改寫的交付物是讀者可遷移的判讀框架，把原文變順只是表層。沒有框架、預警與下一步路由，文章仍停在摘要層。</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></channel></rss>