<?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>Placeholder on Tarragon</title><link>https://tarrragon.github.io/blog/tags/placeholder/</link><description>Recent content in Placeholder on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 17 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/placeholder/index.xml" rel="self" type="application/rss+xml"/><item><title>Placeholder</title><link>https://tarrragon.github.io/blog/ddd/knowledge-cards/placeholder/</link><pubDate>Mon, 13 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/ddd/knowledge-cards/placeholder/</guid><description>&lt;p>佔位（placeholder）是「先立介面、實作後補」的合法開發中間態：指向「開發中」頁的路由、空的事件 callback、拋出「requires override」的 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/dependency-injection/" data-link-title="Dependency Injection" data-link-desc="物件的依賴該由誰提供、測試怎麼換掉真實依賴時使用。依賴注入把「建構依賴」跟「使用依賴」分成兩個責任——使用方宣告需要什麼、提供方在組裝時決定給什麼。">注入項&lt;/a>、回傳 hardcoded 假資料的 stub。節奏本身沒有問題、問題在佔位的失效形態：漏網的佔位通過所有以 mock 為基礎的驗收、終點站是使用者的回報。與它相鄰的組裝概念見 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/composition-root/" data-link-title="Composition Root" data-link-desc="依賴組裝、路由註冊該集中在哪、組裝斷裂去哪檢查時使用。composition root 是應用程式唯一的組裝起點——DI、路由、事件接線的集中處。">composition root&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>失效的靜默程度比文件層約束（層次判準見 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/invariant/" data-link-title="Invariant" data-link-desc="領域模型的約束規則落在哪一層時使用。不變式是在物件整個生命週期都必須為真的業務規則——狀態只能沿流程轉換、被同一條規則綁住的欄位必須一起換。">invariant&lt;/a>）再深一級：文件層失效至少留下「規則寫在那裡、沒人遵守」的對照證據；佔位讓測試綠燈——型別層看它是合法構件、行為測試的 override 讓它永遠沒被觸發（override 的機制見 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/test-seam/" data-link-title="Test Seam" data-link-desc="測試想把真實依賴換成替身、該從哪裡換時使用。seam 是不修改程式本體就能替換其中一段行為的位置——介面加注入點是物件導向最常見的形式。">test seam&lt;/a>）。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>佔位頁的型別名、「requires override」的拋出語句、空函式體都是靜態可掃描的形態。回傳假值的佔位在掃描與 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/wiring-test/" data-link-title="Wiring Test" data-link-desc="行為測試全綠、還想知道組裝有沒有完成時使用。接線測試在組裝路徑零 override 的環境解析每個注入項、走真實路由表，只驗「port 插上了 adapter」。">接線測試&lt;/a> 眼中都是正常構件——注入項解析得了、導航也會發生。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>佔位需要一個獨立的攔截點：靜態可掃描的形態進發版前置檢查、掃到即警告、由人判斷是刻意中間態還是漏網；回傳假值的那一類靠行為斷言或實機冒煙走查。攔截點怎麼落進發版流程、&lt;a href="https://tarrragon.github.io/blog/ddd/composition-root-reachability/" data-link-title="組裝層的可達性" data-link-desc="行為測試全綠、功能在實機上沒有入口的失效形態出現時使用。mock 換掉的正是組裝，組裝完成與否在行為測試裡沒有證言；把可達性當成組裝層的不變式，在測試、發版與設計文件各給一個強制點。">組裝層的可達性&lt;/a> 有完整處置。&lt;/p></description><content:encoded><![CDATA[<p>佔位（placeholder）是「先立介面、實作後補」的合法開發中間態：指向「開發中」頁的路由、空的事件 callback、拋出「requires override」的 <a href="/blog/ddd/knowledge-cards/dependency-injection/" data-link-title="Dependency Injection" data-link-desc="物件的依賴該由誰提供、測試怎麼換掉真實依賴時使用。依賴注入把「建構依賴」跟「使用依賴」分成兩個責任——使用方宣告需要什麼、提供方在組裝時決定給什麼。">注入項</a>、回傳 hardcoded 假資料的 stub。節奏本身沒有問題、問題在佔位的失效形態：漏網的佔位通過所有以 mock 為基礎的驗收、終點站是使用者的回報。與它相鄰的組裝概念見 <a href="/blog/ddd/knowledge-cards/composition-root/" data-link-title="Composition Root" data-link-desc="依賴組裝、路由註冊該集中在哪、組裝斷裂去哪檢查時使用。composition root 是應用程式唯一的組裝起點——DI、路由、事件接線的集中處。">composition root</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>失效的靜默程度比文件層約束（層次判準見 <a href="/blog/ddd/knowledge-cards/invariant/" data-link-title="Invariant" data-link-desc="領域模型的約束規則落在哪一層時使用。不變式是在物件整個生命週期都必須為真的業務規則——狀態只能沿流程轉換、被同一條規則綁住的欄位必須一起換。">invariant</a>）再深一級：文件層失效至少留下「規則寫在那裡、沒人遵守」的對照證據；佔位讓測試綠燈——型別層看它是合法構件、行為測試的 override 讓它永遠沒被觸發（override 的機制見 <a href="/blog/ddd/knowledge-cards/test-seam/" data-link-title="Test Seam" data-link-desc="測試想把真實依賴換成替身、該從哪裡換時使用。seam 是不修改程式本體就能替換其中一段行為的位置——介面加注入點是物件導向最常見的形式。">test seam</a>）。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>佔位頁的型別名、「requires override」的拋出語句、空函式體都是靜態可掃描的形態。回傳假值的佔位在掃描與 <a href="/blog/ddd/knowledge-cards/wiring-test/" data-link-title="Wiring Test" data-link-desc="行為測試全綠、還想知道組裝有沒有完成時使用。接線測試在組裝路徑零 override 的環境解析每個注入項、走真實路由表，只驗「port 插上了 adapter」。">接線測試</a> 眼中都是正常構件——注入項解析得了、導航也會發生。</p>
<h2 id="設計責任">設計責任</h2>
<p>佔位需要一個獨立的攔截點：靜態可掃描的形態進發版前置檢查、掃到即警告、由人判斷是刻意中間態還是漏網；回傳假值的那一類靠行為斷言或實機冒煙走查。攔截點怎麼落進發版流程、<a href="/blog/ddd/composition-root-reachability/" data-link-title="組裝層的可達性" data-link-desc="行為測試全綠、功能在實機上沒有入口的失效形態出現時使用。mock 換掉的正是組裝，組裝完成與否在行為測試裡沒有證言；把可達性當成組裝層的不變式，在測試、發版與設計文件各給一個強制點。">組裝層的可達性</a> 有完整處置。</p>
]]></content:encoded></item><item><title>U.C20 管理模式操作全是佔位 — dev toast 讓未接線看起來有反應</title><link>https://tarrragon.github.io/blog/ux-design/cases/management-actions-placeholder-only/</link><pubDate>Fri, 17 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/ux-design/cases/management-actions-placeholder-only/</guid><description>&lt;p>書庫管理 app 的管理模式有完整的批次操作 domain 服務、畫面上也有一整排批次操作按鈕 — 兩者之間的接線全數是佔位。佔位 handler 的風險是雙重的：對使用者，可點的假按鈕比沒有按鈕更糟（按鈕的存在承諾功能存在）；對開發者，接了 dev toast / log 的佔位在自測時「有反應」，讓未接線在驗收前不可見。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>驗收回報「右下按鈕按了沒反應」；管理模式批次操作 UI 的盤點結果：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>UI 元件&lt;/th>
 &lt;th>onPressed 實際行為&lt;/th>
 &lt;th>位置&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>右下浮動按鈕 FAB（「批次操作」）&lt;/td>
 &lt;td>只彈開發用的點擊事件測試 toast&lt;/td>
 &lt;td>&lt;code>library_display_extensions.dart:62-66&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>底部欄「編輯」「分享」「刪除」&lt;/td>
 &lt;td>只寫 log「&amp;hellip;尚未實作」&lt;/td>
 &lt;td>&lt;code>library_display_page.dart:129,144,159&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AppBar「更多選項」&lt;/td>
 &lt;td>只寫 log「尚未實作」&lt;/td>
 &lt;td>&lt;code>library_display_page.dart:63-67&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>對照規格（SPEC-006 FR-9 / FR-10）：批次操作的 domain 層已完整實作 — &lt;code>LibraryManagementService.performBatchOperation&lt;/code>（刪除 / 改來源 / 改重要度 / 增刪標籤）與 Command 模式的編輯服務（undo / redo）都在 — &lt;strong>是 UI 到 service 的最後一段接線沒做&lt;/strong>。團隊已有「佔位實作」的追蹤票（佔位掃描盲區改善），本案是同類技術債在管理模式的集中呈現。&lt;/p>
&lt;p>附帶：底部欄只在「已選取至少一本」時才出現 — 驗收者若沒先勾書、連佔位的編輯 / 分享 / 刪除鍵都看不到。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>可點的假按鈕是負資產&lt;/strong>。按鈕存在 = 系統承諾功能存在，點了沒反應被讀成「壞掉」（體感同 U.C5 零回饋）— 對產品信任的傷害大於「功能還沒有」。未完成的功能在 UI 上的正確形態是隱藏、或 disabled + 說明（「即將推出」），不是可點的佔位。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>dev toast 佔位掩蓋未接線&lt;/strong>。接了 toast 的按鈕在開發自測時「有反應」— 開發回饋（toast 彈了）與使用者回饋（操作確實執行）被混為一談，佔位從此不在任何人的視野裡，直到驗收。log-only 佔位更隱形：畫面上完全無反應。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>domain 完成 + UI 佔位 = 完成度斷層&lt;/strong>。規格對照表寫「批次管理操作：已實作」指的是 service 層 — 進度報告以 domain 層為準時，UI 未接線的斷層不會出現在任何狀態欄位上，驗收是唯一暴露點。完成度要分層記帳（domain / UI 接線 / 驗收通過）。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>佔位 handler 用統一可掃描的標記&lt;/strong>（固定的 dev-placeholder helper、或 &lt;code>// PLACEHOLDER:&lt;/code> 類統一註解），release 前用 grep 掃描（本案的現成過渡掃法：&lt;code>rg &amp;quot;尚未實作|DevToast&amp;quot;&lt;/code>）清零或轉成 disabled + 說明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>未接線功能的 UI 三選一&lt;/strong>：隱藏（功能不該被發現）、disabled + 原因（讓使用者知道存在但未開放）、接線（完成它）— 可點無反應不在選項裡。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>完成度分層記帳&lt;/strong>：domain 完成、UI 接線、驗收通過是三個獨立狀態 — 「已實作」要標明層級，避免 UI 斷層藏在 domain 的完成宣告後面。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>零回饋按鈕的原型案例 → &lt;a href="https://tarrragon.github.io/blog/ux-design/cases/export-button-zero-feedback/" data-link-title="U.C5 匯出按鈕按下零回饋 — 狀態機完備但 UI 沒接線" data-link-desc="Flutter app 匯出設定頁的確認匯出按鈕 onPressed 是空 callback，按下畫面毫無變化 — 使用者無法分辨匯出成功、進行中、還是功能根本沒做。ViewModel 的 idle/inProgress/completed/failed 狀態機早已完備，缺的只是頁面接線與三層回饋">U.C5 匯出按鈕零回饋&lt;/a>&lt;/li>
&lt;li>回饋鏈路的其他斷點 → &lt;a href="https://tarrragon.github.io/blog/ux-design/cases/async-listener-false-failure/" data-link-title="U.C9 提取成功卻誤報失敗 — 結果通知鏈路被搶通道" data-link-desc="操作實際成功、資料已寫入，UI 卻顯示失敗時使用。多 context 的訊息通道語意（誰負責回應）是結果通知鏈路的一部分，async listener 搶通道會把 undefined 當成回應送回">U.C9 提取成功卻誤報失敗&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/selection-count-layout-starvation/" data-link-title="U.C19 「已選擇」計數被版面擠壓成省略號 — 回饋死在 layout" data-link-desc="狀態文字顯示成「...」、資料層查起來卻一切正常 — 回饋鏈的最後一哩是版面，flex 寬度競爭把關鍵計數整串壓成省略號，state 正確、使用者拿到零資訊">U.C19 計數被版面擠壓&lt;/a>&lt;/li>
&lt;li>三層回饋的完整要求 → &lt;a href="https://tarrragon.github.io/blog/ux-design/06-interaction-feedback/feedback-three-layers/" data-link-title="互動回饋三層模型：點擊確認、等待指示、結果通知" data-link-desc="使用者操作後的回饋依時間分層，缺層的症狀是重複提交與重複導航 — 診斷「按了沒反應」與多步驟流程卡狀態問題的檢查框架，涵蓋按鈕級與畫面級兩個尺度。">互動回饋三層模型&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>書庫管理 app 的管理模式有完整的批次操作 domain 服務、畫面上也有一整排批次操作按鈕 — 兩者之間的接線全數是佔位。佔位 handler 的風險是雙重的：對使用者，可點的假按鈕比沒有按鈕更糟（按鈕的存在承諾功能存在）；對開發者，接了 dev toast / log 的佔位在自測時「有反應」，讓未接線在驗收前不可見。</p>
<h2 id="觀察">觀察</h2>
<p>驗收回報「右下按鈕按了沒反應」；管理模式批次操作 UI 的盤點結果：</p>
<table>
  <thead>
      <tr>
          <th>UI 元件</th>
          <th>onPressed 實際行為</th>
          <th>位置</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>右下浮動按鈕 FAB（「批次操作」）</td>
          <td>只彈開發用的點擊事件測試 toast</td>
          <td><code>library_display_extensions.dart:62-66</code></td>
      </tr>
      <tr>
          <td>底部欄「編輯」「分享」「刪除」</td>
          <td>只寫 log「&hellip;尚未實作」</td>
          <td><code>library_display_page.dart:129,144,159</code></td>
      </tr>
      <tr>
          <td>AppBar「更多選項」</td>
          <td>只寫 log「尚未實作」</td>
          <td><code>library_display_page.dart:63-67</code></td>
      </tr>
  </tbody>
</table>
<p>對照規格（SPEC-006 FR-9 / FR-10）：批次操作的 domain 層已完整實作 — <code>LibraryManagementService.performBatchOperation</code>（刪除 / 改來源 / 改重要度 / 增刪標籤）與 Command 模式的編輯服務（undo / redo）都在 — <strong>是 UI 到 service 的最後一段接線沒做</strong>。團隊已有「佔位實作」的追蹤票（佔位掃描盲區改善），本案是同類技術債在管理模式的集中呈現。</p>
<p>附帶：底部欄只在「已選取至少一本」時才出現 — 驗收者若沒先勾書、連佔位的編輯 / 分享 / 刪除鍵都看不到。</p>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>可點的假按鈕是負資產</strong>。按鈕存在 = 系統承諾功能存在，點了沒反應被讀成「壞掉」（體感同 U.C5 零回饋）— 對產品信任的傷害大於「功能還沒有」。未完成的功能在 UI 上的正確形態是隱藏、或 disabled + 說明（「即將推出」），不是可點的佔位。</p>
</li>
<li>
<p><strong>dev toast 佔位掩蓋未接線</strong>。接了 toast 的按鈕在開發自測時「有反應」— 開發回饋（toast 彈了）與使用者回饋（操作確實執行）被混為一談，佔位從此不在任何人的視野裡，直到驗收。log-only 佔位更隱形：畫面上完全無反應。</p>
</li>
<li>
<p><strong>domain 完成 + UI 佔位 = 完成度斷層</strong>。規格對照表寫「批次管理操作：已實作」指的是 service 層 — 進度報告以 domain 層為準時，UI 未接線的斷層不會出現在任何狀態欄位上，驗收是唯一暴露點。完成度要分層記帳（domain / UI 接線 / 驗收通過）。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li>
<p><strong>佔位 handler 用統一可掃描的標記</strong>（固定的 dev-placeholder helper、或 <code>// PLACEHOLDER:</code> 類統一註解），release 前用 grep 掃描（本案的現成過渡掃法：<code>rg &quot;尚未實作|DevToast&quot;</code>）清零或轉成 disabled + 說明。</p>
</li>
<li>
<p><strong>未接線功能的 UI 三選一</strong>：隱藏（功能不該被發現）、disabled + 原因（讓使用者知道存在但未開放）、接線（完成它）— 可點無反應不在選項裡。</p>
</li>
<li>
<p><strong>完成度分層記帳</strong>：domain 完成、UI 接線、驗收通過是三個獨立狀態 — 「已實作」要標明層級，避免 UI 斷層藏在 domain 的完成宣告後面。</p>
</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>零回饋按鈕的原型案例 → <a href="/blog/ux-design/cases/export-button-zero-feedback/" data-link-title="U.C5 匯出按鈕按下零回饋 — 狀態機完備但 UI 沒接線" data-link-desc="Flutter app 匯出設定頁的確認匯出按鈕 onPressed 是空 callback，按下畫面毫無變化 — 使用者無法分辨匯出成功、進行中、還是功能根本沒做。ViewModel 的 idle/inProgress/completed/failed 狀態機早已完備，缺的只是頁面接線與三層回饋">U.C5 匯出按鈕零回饋</a></li>
<li>回饋鏈路的其他斷點 → <a href="/blog/ux-design/cases/async-listener-false-failure/" data-link-title="U.C9 提取成功卻誤報失敗 — 結果通知鏈路被搶通道" data-link-desc="操作實際成功、資料已寫入，UI 卻顯示失敗時使用。多 context 的訊息通道語意（誰負責回應）是結果通知鏈路的一部分，async listener 搶通道會把 undefined 當成回應送回">U.C9 提取成功卻誤報失敗</a>、<a href="/blog/ux-design/cases/selection-count-layout-starvation/" data-link-title="U.C19 「已選擇」計數被版面擠壓成省略號 — 回饋死在 layout" data-link-desc="狀態文字顯示成「...」、資料層查起來卻一切正常 — 回饋鏈的最後一哩是版面，flex 寬度競爭把關鍵計數整串壓成省略號，state 正確、使用者拿到零資訊">U.C19 計數被版面擠壓</a></li>
<li>三層回饋的完整要求 → <a href="/blog/ux-design/06-interaction-feedback/feedback-three-layers/" data-link-title="互動回饋三層模型：點擊確認、等待指示、結果通知" data-link-desc="使用者操作後的回饋依時間分層，缺層的症狀是重複提交與重複導航 — 診斷「按了沒反應」與多步驟流程卡狀態問題的檢查框架，涵蓋按鈕級與畫面級兩個尺度。">互動回饋三層模型</a></li>
</ul>
]]></content:encoded></item></channel></rss>