fix(panels): keep a scaled panel's layout box at its natural height - #118
Conversation
A panel taller than the usable screen was scaled to fit by usageApplyPanelZoom, but on Windows it still lost its bottom rows: every card was clipped by a different amount and a blank strip sat under the footer. Two separate faults stacked up. _apply_panel_zoom ran inside _place_window_on_ui_thread, so window.evaluate_js was called from the WinForms UI thread. pywebview waits synchronously for WebView2's ExecuteScriptAsync completion, which is dispatched back to that same thread -- the call never returned. A live trace showed _apply_panel_zoom ENTER with InvokeRequired=False and no matching EXIT. The scale is now resolved and applied from the calling (background) thread, and only the resize/move are dispatched to the UI thread; the window is not shrunk unless the document confirms it scaled. The second fault outlived the first. Chromium's CSS zoom scales paint output but leaves the layout box at the viewport height, so the content was still laid out against the unscaled window: .wrap children are flex: 0 1 auto over overflow: hidden cards, so they shrank proportionally and each clipped its own content, while the scaled paint left the body 89px short of the window. Measured on WebView2 at scale 0.912848, natural height 1113 into a 1016 viewport, every card was clipped -- claude by 41, projects by 49, footer by 27. WebKit expands the layout box under zoom, which is why macOS was unaffected. usageApplyPanelZoom now takes the natural height alongside the scale and gives the body that height before zooming, so flex children lay out against the space they actually need. Both shells pass the height they already know -- panel_height() on Windows, resolve_panel_size() on macOS, never window.innerHeight / scale, which would double-compensate on WebKit. macOS caches (scale, height) so a height change at an unchanged scale is still applied. naturalContentHeight() releases the compensation alongside zoom while measuring, so the value cannot ratchet upward. Re-measured on WebView2: body height equals the window, and every card's layout height covers its content -- claude 184/182, grok 118/116, projects 209/208, footer 159/158. Full suite 1554 passed; ruff, mypy, and check_file_size.py clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Es6czYMZ99nN8ma4QxZ98V
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ecfd8e521c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| current_position = self._current_window_position() | ||
| work_area = self._work_area_for_point(current_position) or self._working_area() |
There was a problem hiding this comment.
Derive zoom from the target monitor
When the current native position and target anchor are on monitors with different work-area heights, this calculates the DOM zoom for the wrong monitor. For example, on the first show the hidden window may currently be on the primary monitor while _place_window_on_ui_thread() uses a saved position on a shorter secondary monitor; the page receives scale 1 here, but lines 1047-1053 resize it using the secondary monitor's smaller scale, recreating clipped content. The reverse mismatch also occurs with force_default=True. Resolve the same anchor/work area used by the queued placement before applying zoom, or pass the already-computed scale through to the UI mutation.
Useful? React with 👍 / 👎.
問題
面板高於可用螢幕時會縮放以塞進畫面,但 Windows 上仍然缺底部內容:每張卡片被裁掉的量都不一樣,footer 下方還多出一條空白。兩個獨立的缺陷疊在一起。
缺陷一:zoom 呼叫在 UI 執行緒上自鎖
_apply_panel_zoom原本在_place_window_on_ui_thread裡執行,等於在 WinForms UI 執行緒上呼叫window.evaluate_js。pywebview 會同步等待 WebView2 的ExecuteScriptAsync完成,而該完成事件又被派回同一條執行緒——呼叫永遠不返回。活體追蹤:
現在改為在呼叫端(背景執行緒)解析並套用縮放,只把 resize/move 派給 UI 執行緒;文件端沒確認縮放成功就不縮小視窗。
缺陷二:Chromium 的 CSS zoom 不放大版面空間
zoom只縮小繪製結果,版面框仍維持視窗高度,內容因此還是照未縮放的尺寸排版。.wrap的子層是flex: 0 1 auto且.card/.footer都overflow: hidden,於是全部被等比壓縮、各自裁掉自己的內容;縮小後的繪製結果又比視窗矮 89px。WebView2 實測(scale 0.912848,自然高度 1113 塞進 1016):
WebKit 在 zoom 下會連版面框一起放大,所以 macOS 不受影響。
修法
usageApplyPanelZoom改為同時接收自然高度,先把 body 撐開到該高度再套用 zoom,讓 flex 子層依照真正需要的空間排版。兩個殼層各自傳入它已知的高度——Windows 用panel_height(),macOS 用resolve_panel_size()——不使用window.innerHeight / scale,那在 WebKit 上會雙重補償。macOS 端快取改成(scale, height),確保高度變動但比例相同時仍會重新套用。naturalContentHeight()量測時會連同 zoom 一起解除補償,避免量測值逐次放大。驗證
WebView2 重新量測:body 高度等於視窗(底部留白 0),每張卡片的版面高度都覆蓋其內容——claude 184/182、grok 118/116、projects 209/208、footer 159/158,裁切卡片 0 張。
macOS 專屬檢查(
check_panel_parity.py、tests/test_menubar.py)需 PyObjC,在 Windows 無法執行,交由 CI 驗證。🤖 Generated with Claude Code
https://claude.ai/code/session_01Es6czYMZ99nN8ma4QxZ98V