diff --git a/html/work-package-suite-styles.css b/html/work-package-suite-styles.css
index 06027b1..6199e45 100644
--- a/html/work-package-suite-styles.css
+++ b/html/work-package-suite-styles.css
@@ -31,23 +31,43 @@ body {
min-height: 100vh;
}
-/* HEADER — dark UI Shell bar */
+/* HEADER — dark UI Shell bar
+ Four groups share this bar, not two: .header-left and .header-right are in the
+ markup, and wp-chrome.js injects the project switcher and search between them
+ while auth-guard.js appends the user menu after. At 1024px that is more than
+ fits on one 48px line, and a fixed height meant the overflow had nowhere to go
+ but on top of its neighbours. min-height + wrap lets the bar grow instead.
+ One row still measures exactly 48px, so nothing moves at desk width. */
.header {
background: var(--appbar);
color: #fff;
padding: 0 16px;
- height: 48px;
+ min-height: 48px;
display: flex;
+ flex-wrap: wrap;
+ row-gap: 6px;
justify-content: space-between;
align-items: center;
}
+/* Sizes to its content and never narrower than the logo.
+ This was `flex: 1; min-width: 0`, which put it in a dead tie with the chrome
+ wp-chrome.js injects as its sibling (`flex: 1 1 auto; min-width: 0`): both
+ claimed the same run of the bar, both were allowed to shrink to nothing, and
+ the chrome's wider content won every time. .header-left computed to 0 while
+ its `flex-shrink: 0` logo kept its 106px and overflowed — so the project
+ switcher rendered straight across the logo (F3).
+ Now the chrome is the only one that grows, and `min-width: auto` restores the
+ content-based floor, which is the logo plus the gap: the inner title block
+ keeps its own `min-width: 0`, so the project name still gives way first, by
+ the ellipsis .header-subtitle already carries. Truncation policy itself is
+ B2's, not this task's. */
.header-left {
- flex: 1;
+ flex: 0 1 auto;
display: flex;
align-items: center;
gap: 14px;
- min-width: 0;
+ min-width: auto;
}
/* Prime logo (white-background wordmark) sits in a white chip on the dark bar */
@@ -95,10 +115,16 @@ body {
text-overflow: ellipsis;
}
+/* Holds its natural size. Left to the default `flex-shrink: 1` it was squeezed
+ narrower than its buttons, so "Load Sample" ran underneath "Feedback". The
+ chrome between the two groups is the only flexible one, which is what gives
+ the three a defined order of giving way: right keeps its buttons, left keeps
+ its logo, the middle absorbs the difference. */
.header-right {
display: flex;
align-items: center;
gap: 8px;
+ flex: 0 0 auto;
}
.header-button {
diff --git a/tests/f_items.py b/tests/f_items.py
index 5170e60..a9187ae 100644
--- a/tests/f_items.py
+++ b/tests/f_items.py
@@ -204,15 +204,41 @@ def f3(page, base, tok):
wait_for="!!document.querySelector('.header-left')")
time.sleep(0.9)
got = page.eval("""(function(){
- var hl=document.querySelector('.header-left'),
- logo=document.querySelector('.header-left .logo'),
- ch=document.querySelector('.wpc-proj')||document.querySelector('.wp-chrome');
- if(!hl||!logo||!ch) return 'null';
- var L=logo.getBoundingClientRect(), C=ch.getBoundingClientRect();
- var ox=Math.min(C.right,L.right)-Math.max(C.left,L.left);
- var oy=Math.min(C.bottom,L.bottom)-Math.max(C.top,L.top);
+ var hdr=document.querySelector('.header'),
+ hl=document.querySelector('.header-left'),
+ logo=document.querySelector('.header-left .logo');
+ if(!hdr||!hl||!logo) return 'null';
+ // Every group sharing this bar, not just the two the review named: the
+ // markup's own, plus what wp-chrome.js and auth-guard.js inject into it.
+ var boxes=[];
+ ['.header-left','.wp-chrome','.header-right','#wp-usermenu'].forEach(function(s){
+ var e=hdr.querySelector(s); if(!e) return;
+ var r=e.getBoundingClientRect();
+ if(r.width>0||r.height>0) boxes.push({s:s,r:r});
+ });
+ var pairs=[];
+ for(var i=0;i1&&oy>1) pairs.push(boxes[i].s+'/'+boxes[j].s+' '+Math.round(ox)+'x'+Math.round(oy));
+ }
+ // The logo is flex-shrink:0 and keeps its width even when its parent is
+ // crushed, so check it against the chrome directly too.
+ var ch=hdr.querySelector('.wpc-proj')||hdr.querySelector('.wp-chrome');
+ var lox=0,loy=0;
+ if(ch){var L=logo.getBoundingClientRect(),C=ch.getBoundingClientRect();
+ lox=Math.min(C.right,L.right)-Math.max(C.left,L.left);
+ loy=Math.min(C.bottom,L.bottom)-Math.max(C.top,L.top);}
+ // Anything spilling past the bar lands on the tab row underneath it.
+ var H=hdr.getBoundingClientRect(), spill=0;
+ [].forEach.call(hdr.children,function(e){
+ var r=e.getBoundingClientRect();
+ if(r.width===0&&r.height===0) return;
+ if(r.bottom>H.bottom+1||r.top0,
hlsw:hl.scrollWidth, hlcw:hl.clientWidth,
name:((document.querySelector('.wpc-proj-name')||{}).textContent||'').trim()});})()""")
@@ -225,6 +251,10 @@ def f3(page, base, tok):
why.append(f"chrome over the logo by {o['ox']}x{o['oy']}px")
if o["collapsed"]:
why.append(f".header-left collapsed to 0 (content {o['hlsw']}px)")
+ if o["pairs"]:
+ why.append("overlapping groups: " + "; ".join(o["pairs"]))
+ if o["spill"]:
+ why.append(f"{o['spill']} group(s) spilling out of the bar")
if why:
hits.append(f"{w}px: " + ", ".join(why))
else: