Skip to content

fix(ui): scale auxiliary panels to dvh when both open at narrow widths - #830

Merged
Astro-Han merged 1 commit into
mainfrom
fix/824-both-present-shared-height
Jul 12, 2026
Merged

Astro-Han merged 1 commit into
mainfrom
fix/824-both-present-shared-height

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jul 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • At <=990px, when both BrowserPanel and a non-collapsed ArtifactPane are open, scale each auxiliary panel's min-height to min(22dvh, 220px) (via a :has()-scoped rule) so the chat row keeps 56% of the content viewport instead of being squeezed to 0 by two fixed 220px minima.
  • Enforce the 320px window floor at runtime via BrowserWindow minHeight: SAFE_MIN_HEIGHT, extracting SAFE_MIN_WIDTH / SAFE_MIN_HEIGHT from window-state.ts so the resize floor and the sanitizeBounds restore floor share one source of truth.
  • Lock both with CSS source contracts.

Why

Closes #824. At <=990px both ArtifactPane and BrowserPanel become bottom sheets, each with a fixed min-height: 220px (from #818). When both are open and the detail area is short (<=~440px), the two fixed minima squeeze the chat row minmax(0, 1fr) to 0 and the composer is clipped. The 320px restore minimum in window-state.ts was restore-only and BrowserWindow set no minHeight, so runtime drag could go shorter. #818's own comment flagged this edge and tracked the shared-height policy in #824.

Scope

  • In scope: the both-present + short-window case at <=990px; aligning the BrowserWindow runtime height floor with the sanitizeBounds restore floor.
  • Out of scope: the single-panel narrow case (fix(ui): give BrowserPanel a narrow bottom-sheet layout (#546) #818); the <=990px breakpoint; the bottom-sheet pattern; minWidth enforcement (width isn't the clip cause — minWidth coherence with sanitizeBounds is a separate follow-up); a useContentSize migration (the window-vs-content frame relationship is pre-existing, see Verification).

Verification

  • TDD: both-present shared-height contract + BrowserWindow minHeight contract each written RED first, then GREEN. 4/4 in browser-panel-layout.test.ts; 13/13 in app-region-hygiene-contract.test.ts; 7/7 in window-state.test.ts.
  • npm --workspace @maka/desktop run typecheck (main + renderer + storybook) — pass.
  • npm --workspace @maka/desktop test (full main suite) — 2354/2354 pass.
  • No visual-smoke baseline impact: the :has() rule fires only when both BrowserPanel and a non-collapsed ArtifactPane are present, and no existing fixture seeds a browser panel (the browser fixture is feat(ui): add deterministic visual-smoke fixture + DOM contract for BrowserPanel renderer chrome #819). So the rule never applies in any current fixture; artifact-pane / artifact-errors / artifact-preview-* narrow baselines are unchanged. The screenshots are 820px tall × 990/1280 wide, all above the 320px minHeight, so the window floor doesn't affect them either.
  • Visual evidence (screenshot/CDP of the both-present state) is not applicable here because no fixture triggers the rule yet; the substitute evidence is the CSS source contract (locks the rule's exact selector + value + collapsed guard + single-declaration) plus the dvh math below.
  • dvh math: the chat row becomes 56% of the content viewport (1 - 2×22dvh), so the composer (~164px + 8px margin) stays usable while the content viewport is >=~307px (172 / 0.56). min(22dvh, 220px) shrinks below 220px below 1000px tall and caps at 220px above (so >=1000px tall both-present is unchanged); at the 320px window floor the panels are ~70px each (toolbar + thin strip), recovery buttons reachable.
  • Platform floor (honest): minHeight: SAFE_MIN_HEIGHT (320) enforces the 320px WINDOW floor. On macOS (hiddenInset titlebar) the content viewport ≈ window, so the composer-usable floor is ~320px. On Windows (titleBarOverlay ~30px) and Linux (default frame ~35px) the frame reduces the content viewport, so the effective composer-usable floor is a slightly larger window (~335px). This window-vs-content gap is the pre-existing relationship inherited from sanitizeBounds (a 320px window was never a 320px content viewport on Windows/Linux); a useContentSize migration that would let the floor be expressed in content terms is out of fix(ui): reserve chat/composer height when both ArtifactPane and BrowserPanel are open at narrow widths #824 scope. The fix is a net improvement on all platforms (from chat-row-0 at <~440px to chat-row-56%-of-content, usable at content >=~301px).

Impact

  • User-visible: at narrow width (<=990px) with both auxiliary panels open, short windows no longer clip the composer; the panels shrink proportionally instead. Normal-height and tall windows are unchanged (min(22dvh, 220px) == 220px at >=1000px tall). The window can no longer be dragged below 320px tall (aligned with the restore floor).
  • Compatibility / migration: sanitizeBounds still accepts 320px-tall windows at restore (unchanged); BrowserWindow now enforces 320px-tall at resize. Existing saved windows >=320px tall are unaffected.
  • Release note: "Fixed the composer being clipped when both the artifact pane and browser are open in a short, narrow window."

Reviewer notes

  • Why :has() over unconditional dvh: unconditional min-height: min(22dvh, 220px) on both panels would change the single-panel artifact-pane narrow baselines (220px → 180px at the 820px screenshot height). The :has()-scoped rule changes only the both-present case (no existing fixture) and leaves single-panel untouched.
  • Why dvh-budget over a composer min-height floor: a minmax(composer-min, 1fr) chat-row floor would push overflow into the auxiliary rows, clipping the ArtifactPane collapse button (~220px down) and breaking the recovery path fix(ui): reserve chat/composer height when both ArtifactPane and BrowserPanel are open at narrow widths #824 noted. The dvh-budget makes the panels yield, keeping both panels + the composer visible and recovery reachable.
  • Why min(22dvh, 220px) over bare 22dvh: bare 22dvh grows above 220px at tall windows (264px at 1200px, 352px at 1600px) and would exceed the max-height: 360px cap (min-height wins over max-height in conflict) — a tall-window regression. min(22dvh, 220px) caps at 220px so >=1000px tall is unchanged.
  • Why minHeight: SAFE_MIN_HEIGHT (320) and not a higher platform-aware value: 320 is the sanitizeBounds restore floor (the owner of the minimum-size contract); aligning the runtime floor to it is the non-duplicate owner-level fix. A higher platform-specific magic number would decouple from sanitizeBounds and change restore behavior. The residual Windows/Linux content gap at the 320px window floor is the pre-existing frame relationship, documented in the CSS comment, not a fix(ui): reserve chat/composer height when both ArtifactPane and BrowserPanel are open at narrow widths #824-introduced regression.
  • Specificity: the :has() compound selector (≈0,5,0) outranks the base .maka-browser-panel / .maka-artifact-pane rules (0,1,0), so min-height: min(22dvh, 220px) wins when both present; the existing min-height: 220px exact-selector contract still passes because the :has() selector is compound, not bare.
  • Contract rigor (text layer): the both-present contract locks the rule's exact two selectors (whitespace-normalized + sorted, string equality — fail-closed for any selector-shape change: removed/wrapped :has(), collapsed-condition leak, mixed selector-list, reordering, wrong target), the exact min(22dvh, 220px) value, exactly one min-height declaration (case-insensitive prop match — catches MIN-HEIGHT), no CSS-escaped property name (catches m\69n-height), and exactly one :has()-scoped panel-targeting min-height rule (catches a same-specificity reordered-:has() competitor). The narrowBrowserMinHeights (fix(ui): give BrowserPanel a narrow bottom-sheet layout (#546) #818) helper is also case-insensitive + escape-fail-closed. This text contract does NOT model the CSS cascade, so a higher-specificity override (e.g. an #id rule) or an all:initial reset could still win — verifying the cascade-effective computed value is the job of the feat(ui): add deterministic visual-smoke fixture + DOM contract for BrowserPanel renderer chrome #819 computed-style fixture (the reliable layer per the Codex review).

Ready for review

  • Verification is complete and the results above are current
  • Impact, compatibility, migration, documentation, and release-note needs are addressed
  • User-visible UI/UX changes include visual evidence, or Verification explains why it is not applicable

@Astro-Han
Astro-Han force-pushed the fix/824-both-present-shared-height branch 7 times, most recently from 1e29d1f to 0a3db05 Compare July 12, 2026 15:31
…w widths

At <=990px both ArtifactPane and BrowserPanel become bottom sheets, each
with a fixed min-height: 220px (from #818). When both are open and the
detail area is short (<=~440px), the two fixed minima squeeze the chat row
`minmax(0, 1fr)` to 0 and the composer is clipped — a reachable state
since the 320px restore minimum in window-state.ts was restore-only and
BrowserWindow set no minHeight, so runtime drag could go shorter.

Fix with two coordinated changes:

1. chat-detail.css: add a :has()-scoped rule inside the 990px block that,
   when BrowserPanel AND a non-collapsed ArtifactPane are both present,
   scales each panel's min-height to min(22dvh, 220px) so the chat row
   keeps the remainder. min(22dvh, 220px) shrinks below 220px at short
   heights and never grows above 220px at tall heights, so >=1000px tall
   (where 22dvh == 220px) is unchanged; below that the panels yield
   proportionally. Single-panel (or a collapsed ArtifactPane) keeps the
   fixed 220px / 34px floor, so the rule scopes to both-present via :has()
   (already used in 8 other places) and skips the collapsed artifact.
   Recovery (close browser / collapse artifact) stays reachable because
   each panel's toolbar sits at the top of a non-zero-height row.

2. main-window.ts + window-state.ts: extract SAFE_MIN_WIDTH / SAFE_MIN_HEIGHT
   from window-state.ts (consolidating the 480x320 literals already used by
   sanitizeBounds + visualSmokeWindowBounds) and set BrowserWindow
   `minHeight: SAFE_MIN_HEIGHT` so the runtime resize floor matches the
   sanitizeBounds restore floor. This makes the 320px floor the dvh fix
   relies on actually enforced at runtime, not just at restore.

Tests:
- browser-panel-layout.test.ts: both-present shared-height contract locking
  the :has() rule's exact `min(22dvh, 220px)` value, both-panel coverage,
  and per-selector that each selector carries the full both-present +
  non-collapsed condition (rejects an unconditional panel selector that
  would leak dvh into single-panel, a collapsed-condition leak, a wrong
  value, or a missing guard).
- app-region-hygiene-contract.test.ts: BrowserWindow `minHeight:
  SAFE_MIN_HEIGHT` contract tying the runtime floor to the shared constant
  exported from window-state.ts.

Closes #824. Refs #546.
@Astro-Han
Astro-Han force-pushed the fix/824-both-present-shared-height branch from 0a3db05 to 49fe726 Compare July 12, 2026 15:36
@Astro-Han
Astro-Han merged commit 755722c into main Jul 12, 2026
3 checks passed
@Astro-Han
Astro-Han deleted the fix/824-both-present-shared-height branch July 14, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(ui): reserve chat/composer height when both ArtifactPane and BrowserPanel are open at narrow widths

1 participant