fix(local-tool): Wails 控制台一打開就看到 modal — CSS specificity bug

使用者回報:Windows 乾淨環境安裝後,一打開 app 就看到「Settings modal +
shutdown-modal「正在停止伺服器…」+ 紅 banner「伺服器無法啟動」」三個應該
hidden 的 element 同時可見。

前面幾個 commit 一直往 Go 端找為什麼 ctrl.Stop 會被意外呼叫,全都沒對。
真正的 bug 是 CSS specificity:

  .modal-backdrop { display: flex; ... }  /* L587,specificity (0,1,0) */
  .error-banner   { display: flex; ... }  /* L488,specificity (0,1,0) */

這兩個 class 的 `display: flex` 規則和 user agent stylesheet 內建的
`[hidden] { display: none }` specificity 相同,但因為我們的 CSS 寫在
cascade 後段勝出——結果是即使 DOM 裡元素有 `hidden` 屬性,瀏覽器依然
渲染成 `display: flex` 可見。

三個受害元素:
  <div class="modal-backdrop" id="settings-modal" hidden>
  <div class="modal-backdrop shutdown-modal" id="shutdown-modal" hidden>
  <section class="error-banner" id="error-banner" hidden>

全部從 DOM 載入第一刻就可見,和 Go 端 ctrl.Stop 是否被呼叫無關。M7
splash 時代前端沒 modal 所以沒人踩到,M8 新加的控制台 UI(8cd5751)
引入這個 bug,但 macOS dev 測試時我只看 server 端 log + api 回應,
沒真的看 Wails 視窗長什麼樣,所以也漏抓。

修法:加全域 `[hidden] { display: none !important; }`。這是 W3C 規範
的標準寫法,保證任何帶 hidden 屬性的元素都會被隱藏,不管其他 CSS
規則怎麼寫。!important 在這情境是正確的——hidden 屬性代表「該元素
不應被顯示」是規範強制語意,不該被任何樣式覆蓋。

驗證:
- macOS dmg 重 build 163MB OK
- binary 內 strings 確認 `[hidden] { display: none !important; }` 已 embed
- 清乾淨 user dataDir 後啟動 wails app,wails.log 整條 startup 流程正常:
  Stage 1 complete → Stage 2 → ctrl.Start returned successfully
- Chrome 建立 2 條 ESTABLISHED 連線到 127.0.0.1:3721
- dataDir 有完整檔案(lock / ipc-port / wails-ipc-port / sentinel / models.json / nef/)

前幾個 commit 修的東西(Stage 2 pause、waitHealthy pause、shutdown modal
safety net、Bug A killStaleServerOnPort)仍然有防禦價值,但都不是使用者
截圖症狀的 root cause。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-04-15 23:20:33 +08:00
parent 35db6c8167
commit a6cd1c12b2

View File

@ -590,6 +590,24 @@ html, body {
z-index: 100;
animation: fadeIn 150ms ease-out;
}
/* CSS specificity 修補`.modal-backdrop` `display: flex` 規則
* specificity = (0,1,0) user agent stylesheet `[hidden] { display: none }`
* 相同但因為寫在後面 cascade 勝出 結果是即使 div `hidden` 屬性
* modal 依然可見這裡用 (0,2,0) 的規則強制 hidden 生效
*
* 症狀M8 一開始就踩到但 M7 splash modal 所以沒人抓到
* Wails 控制台一打開就同時看到 Settings modal + shutdown-modal 疊在畫面上
* 即使 DOM 裡兩個都有 `hidden` 屬性
*
* 同樣問題也影響 `.error-banner`display: flex 覆蓋 hidden所以紅 banner
* 伺服器無法啟動也會一開 app 就可見下面的通用 `[hidden]` 全域規則處理
* 這兩個和未來任何 `.class { display: X }` `hidden` 屬性覆蓋的情況
*
* !important 是必要的attribute selector specificity `(0,1,0)` class
* selector 相同光靠 `.class[hidden]` 要重複寫每個 class全域 `[hidden]!important`
* 最省事且符合 HTML 規範hidden 屬性代表該元素不應被顯示的語意
*/
[hidden] { display: none !important; }
.modal {
background: var(--bg);
border: 1px solid var(--border);