<?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>Ellipsis on Tarragon</title><link>https://tarrragon.github.io/blog/tags/ellipsis/</link><description>Recent content in Ellipsis 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/ellipsis/index.xml" rel="self" type="application/rss+xml"/><item><title>U.C19 「已選擇」計數被版面擠壓成省略號 — 回饋死在 layout</title><link>https://tarrragon.github.io/blog/ux-design/cases/selection-count-layout-starvation/</link><pubDate>Fri, 17 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/ux-design/cases/selection-count-layout-starvation/</guid><description>&lt;p>狀態存在、綁定正確、文字建構無誤，回饋仍然可能死在版面 — 寬度競爭把它壓縮成「&amp;hellip;」，對使用者等於零回饋。回饋鏈的最後一哩是版面。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>書庫管理 app 管理模式的工具列右側有「已選擇: {count}/{total}」文字（&lt;code>library_display_extensions.dart:134-147&lt;/code>）。驗收看到的永遠是「&amp;hellip;」— 第一假設是計數沒接上。&lt;/p>
&lt;p>程式碼事實推翻這個假設：計數來源 &lt;code>selectedBookIds.length / books.length&lt;/code> 是同步、reactive 的，勾選即時更新 — &lt;strong>資料層完全正常&lt;/strong>。「&amp;hellip;」的成因在版面：同一個 Row 裡，模式切換按鈕佔 &lt;code>Flexible(flex: 2)&lt;/code>、中間的 &lt;code>Spacer()&lt;/code> 也參與自由寬度分配，「已選擇」文字分到的份額在窄幕下容不下整串，&lt;code>TextOverflow.ellipsis&lt;/code> 把它壓縮成「&amp;hellip;」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>回饋鏈的驗證要驗到像素層&lt;/strong>。「state 有值、widget 有綁」不等於使用者看得到 — 版面是回饋鏈的最後一環，layout starvation（flex 優先權競爭把某個元件的空間擠到零）讓正確的資料以「&amp;hellip;」呈現。診斷時「資料壞了」和「版面壓掉了」是兩條完全不同的修法，先分清楚再動手。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>ellipsis 對短關鍵資訊是錯誤的降級策略&lt;/strong>。ellipsis 的適用場景是「長文字截尾仍保留頭部資訊」（書名、路徑）；「已選擇: 3/45」這種短計數被截、剩下的「&amp;hellip;」不保留任何資訊。關鍵狀態文字的降級策略應該是縮短格式（「3/45」）、換行、或保障最小寬度 — 不是 ellipsis。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Spacer 與 Flexible 同列是寬度競爭的常見形態&lt;/strong>。Spacer（等同 Expanded）與同列的 flex 元件按比例分配自由寬度 — 文字分到的份額由 flex 配置決定、不由內容需求決定，寬幕開發機上可能剛好夠、窄幕下容不下整串。這類問題只在特定寬度現形，驗收要在窄幕跑。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>等比縮放套件不會攔到這類問題&lt;/strong>。專案全程使用 flutter_screenutil，它做的是設計稿座標到裝置座標的數值換算、工作在 layout 之前的常數層；空間分配發生在 layout 協商層（Row 把有限寬度分給 flex 子元件、輸入是動態內容），換算工具不參與。比例正確不等於空間夠用 —「有用響應式套件」不構成版面不會擠壓的保證，完整判讀見 &lt;a href="https://tarrragon.github.io/blog/report/proportional-scaling-is-not-space-allocation/" data-link-title="等比縮放不管空間分配：用了 sizing 套件、版面仍會擠壓" data-link-desc="用了響應式 / 等比縮放套件、版面仍然溢出或內容被壓縮，懷疑套件失效時使用。換算工具工作在常數層、空間分配發生在 layout 協商層，兩層獨立 — 工具靜默不等於該層無問題，「有用 X 套件」不構成該維度的品質保證">#228 等比縮放不管空間分配&lt;/a>。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>關鍵狀態文字給最小寬度保障或更高的 flex 優先權&lt;/strong>，可壓縮的是裝飾性元素、不是回饋。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>文字降級策略按資訊密度選&lt;/strong>：短計數用縮格式（去掉「已選擇:」前綴、只留「3/45」）、長文字才用 ellipsis。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>診斷「顯示不出來」先分層&lt;/strong>：state 有值嗎（debug 印）→ 綁定對嗎（widget 讀哪個欄位）→ 版面給了空間嗎（layout inspector 看實際寬度）。這個案例卡在第三層。&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/06-interaction-feedback/feedback-three-layers/" data-link-title="互動回饋三層模型：點擊確認、等待指示、結果通知" data-link-desc="使用者操作後的回饋依時間分層，缺層的症狀是重複提交與重複導航 — 診斷「按了沒反應」與多步驟流程卡狀態問題的檢查框架，涵蓋按鈕級與畫面級兩個尺度。">互動回饋三層模型&lt;/a>&lt;/li>
&lt;li>資料正確但 UI 未反映的另一種形態 → &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;/li>
&lt;li>管理模式的佔位操作 → &lt;a href="https://tarrragon.github.io/blog/ux-design/cases/management-actions-placeholder-only/" data-link-title="U.C20 管理模式操作全是佔位 — dev toast 讓未接線看起來有反應" data-link-desc="UI 有按鈕、domain 層功能也寫完、按下去只有開發提示或 log：佔位 handler 比沒有按鈕更糟 — 按鈕的存在承諾功能存在，dev toast 讓開發自測「有反應」、掩蓋未接線">U.C20 管理模式操作全是佔位&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>狀態存在、綁定正確、文字建構無誤，回饋仍然可能死在版面 — 寬度競爭把它壓縮成「&hellip;」，對使用者等於零回饋。回饋鏈的最後一哩是版面。</p>
<h2 id="觀察">觀察</h2>
<p>書庫管理 app 管理模式的工具列右側有「已選擇: {count}/{total}」文字（<code>library_display_extensions.dart:134-147</code>）。驗收看到的永遠是「&hellip;」— 第一假設是計數沒接上。</p>
<p>程式碼事實推翻這個假設：計數來源 <code>selectedBookIds.length / books.length</code> 是同步、reactive 的，勾選即時更新 — <strong>資料層完全正常</strong>。「&hellip;」的成因在版面：同一個 Row 裡，模式切換按鈕佔 <code>Flexible(flex: 2)</code>、中間的 <code>Spacer()</code> 也參與自由寬度分配，「已選擇」文字分到的份額在窄幕下容不下整串，<code>TextOverflow.ellipsis</code> 把它壓縮成「&hellip;」。</p>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>回饋鏈的驗證要驗到像素層</strong>。「state 有值、widget 有綁」不等於使用者看得到 — 版面是回饋鏈的最後一環，layout starvation（flex 優先權競爭把某個元件的空間擠到零）讓正確的資料以「&hellip;」呈現。診斷時「資料壞了」和「版面壓掉了」是兩條完全不同的修法，先分清楚再動手。</p>
</li>
<li>
<p><strong>ellipsis 對短關鍵資訊是錯誤的降級策略</strong>。ellipsis 的適用場景是「長文字截尾仍保留頭部資訊」（書名、路徑）；「已選擇: 3/45」這種短計數被截、剩下的「&hellip;」不保留任何資訊。關鍵狀態文字的降級策略應該是縮短格式（「3/45」）、換行、或保障最小寬度 — 不是 ellipsis。</p>
</li>
<li>
<p><strong>Spacer 與 Flexible 同列是寬度競爭的常見形態</strong>。Spacer（等同 Expanded）與同列的 flex 元件按比例分配自由寬度 — 文字分到的份額由 flex 配置決定、不由內容需求決定，寬幕開發機上可能剛好夠、窄幕下容不下整串。這類問題只在特定寬度現形，驗收要在窄幕跑。</p>
</li>
<li>
<p><strong>等比縮放套件不會攔到這類問題</strong>。專案全程使用 flutter_screenutil，它做的是設計稿座標到裝置座標的數值換算、工作在 layout 之前的常數層；空間分配發生在 layout 協商層（Row 把有限寬度分給 flex 子元件、輸入是動態內容），換算工具不參與。比例正確不等於空間夠用 —「有用響應式套件」不構成版面不會擠壓的保證，完整判讀見 <a href="/blog/report/proportional-scaling-is-not-space-allocation/" data-link-title="等比縮放不管空間分配：用了 sizing 套件、版面仍會擠壓" data-link-desc="用了響應式 / 等比縮放套件、版面仍然溢出或內容被壓縮，懷疑套件失效時使用。換算工具工作在常數層、空間分配發生在 layout 協商層，兩層獨立 — 工具靜默不等於該層無問題，「有用 X 套件」不構成該維度的品質保證">#228 等比縮放不管空間分配</a>。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li>
<p><strong>關鍵狀態文字給最小寬度保障或更高的 flex 優先權</strong>，可壓縮的是裝飾性元素、不是回饋。</p>
</li>
<li>
<p><strong>文字降級策略按資訊密度選</strong>：短計數用縮格式（去掉「已選擇:」前綴、只留「3/45」）、長文字才用 ellipsis。</p>
</li>
<li>
<p><strong>診斷「顯示不出來」先分層</strong>：state 有值嗎（debug 印）→ 綁定對嗎（widget 讀哪個欄位）→ 版面給了空間嗎（layout inspector 看實際寬度）。這個案例卡在第三層。</p>
</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>結果通知的完整設計 → <a href="/blog/ux-design/06-interaction-feedback/feedback-three-layers/" data-link-title="互動回饋三層模型：點擊確認、等待指示、結果通知" data-link-desc="使用者操作後的回饋依時間分層，缺層的症狀是重複提交與重複導航 — 診斷「按了沒反應」與多步驟流程卡狀態問題的檢查框架，涵蓋按鈕級與畫面級兩個尺度。">互動回饋三層模型</a></li>
<li>資料正確但 UI 未反映的另一種形態 → <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></li>
<li>管理模式的佔位操作 → <a href="/blog/ux-design/cases/management-actions-placeholder-only/" data-link-title="U.C20 管理模式操作全是佔位 — dev toast 讓未接線看起來有反應" data-link-desc="UI 有按鈕、domain 層功能也寫完、按下去只有開發提示或 log：佔位 handler 比沒有按鈕更糟 — 按鈕的存在承諾功能存在，dev toast 讓開發自測「有反應」、掩蓋未接線">U.C20 管理模式操作全是佔位</a></li>
</ul>
]]></content:encoded></item></channel></rss>