Michael Rabinovich Cursor commited on
Commit
a435f45
·
1 Parent(s): 2a0d6fb

leaderboard: gallery scroll box sized off screen.availHeight

Browse files

Verified the real cause with a headless harness reproducing HF's nested
iframes: HF auto-resizes the Space iframe to full content height, so
inside the gallery window.innerHeight / parent.innerHeight / vh are all
inflated to the content height (live: 1642 vs a 900 viewport) and only
the cross-origin top window scrolls -- which is why no inner cap or
sticky-against-the-iframe ever worked and the GT row scrolled away.

screen.availHeight is the one viewport-ish measure that survives the
nesting, so size the gallery's own scroll box from it (availHeight minus
a chrome reserve, clamped). The column header + GT row are sticky inside
that box, so they stay locked while the submission rows scroll, and the
box is the single scroller (harness confirms no outer-page overflow
across laptop/desktop/large screens).

Co-authored-by: Cursor <cursoragent@cursor.com>

Files changed (1) hide show
  1. gallery.py +43 -25
gallery.py CHANGED
@@ -250,7 +250,18 @@ body {
250
  .section-caption { margin: 0 0 16px; font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; }
251
  .section-caption b { color: var(--ink); font-weight: 600; }
252
 
253
- .gallery { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); position: relative; }
 
 
 
 
 
 
 
 
 
 
 
254
  .grid-head, .grow {
255
  display: grid;
256
  grid-template-columns: 52px minmax(200px, 1.3fr) 160px repeat(var(--ncol, 4), minmax(140px, 1fr));
@@ -574,39 +585,46 @@ function syncHeadHeight() {
574
  if (head) document.documentElement.style.setProperty('--head-h', head.offsetHeight + 'px');
575
  }
576
 
577
- // Make the gallery the ONLY scroller -- no nested inner+outer scrollbars.
578
- // When the rows are short the iframe shrinks to the content (compact, nothing
579
- // scrolls). Otherwise it fills exactly from its own top down to the bottom of
580
- // the viewport (leaving a little room for the Refresh button below it), so the
581
- // outer Gradio page does not need to scroll and only the iframe's own body
582
- // scrolls -- which is what keeps the sticky column header + ground-truth row
583
- // locked at the top. The available height is measured from the parent (the
584
- // srcdoc iframe is same-origin); if that read is blocked it falls back to the
585
- // content height.
586
- var BOTTOM_RESERVE = 72; // px kept clear below the iframe for the Refresh button
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
  function fitIframe() {
588
  try {
589
  const fe = window.frameElement;
590
- if (!fe) return;
591
- const content = Math.ceil(document.body.scrollHeight);
592
- let target = content;
593
- const pv = window.parent;
594
- if (pv && pv.innerHeight) {
595
- const top = fe.getBoundingClientRect().top; // iframe top in viewport
596
- const avail = Math.floor(pv.innerHeight - top - BOTTOM_RESERVE);
597
- if (avail > 240) target = Math.min(content, avail);
598
- }
599
- fe.style.height = target + 'px';
600
  } catch (e) { /* sandboxed -> keep fallback height */ }
601
  }
602
 
603
  buildGallery();
 
604
  fitIframe();
605
- function relayout() { syncHeadHeight(); fitIframe(); }
606
  window.addEventListener('resize', relayout);
607
- // The viewport height can change without the iframe's own width changing
608
- // (e.g. browser window resized shorter), so also listen on the parent.
609
- try { window.parent.addEventListener('resize', relayout); } catch (e) { /* ignore */ }
610
  if (window.ResizeObserver) new ResizeObserver(fitIframe).observe(document.body);
611
  if (document.fonts && document.fonts.ready) document.fonts.ready.then(relayout);
612
  """
 
250
  .section-caption { margin: 0 0 16px; font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; }
251
  .section-caption b { color: var(--ink); font-weight: 600; }
252
 
253
+ /* The gallery is its own scroll container so the column header + ground-truth
254
+ row (both `position: sticky`) stay locked at the top while the submission
255
+ rows scroll inside it. This is the ONLY scroller for the rows -- it must not
256
+ leak out to the host page. Height is set in JS from `screen.availHeight`
257
+ (the one viewport-ish measure that survives HF's nested iframes, which
258
+ inflate `innerHeight`/`vh` to the full content height); the px value here is
259
+ the pre-script fallback. */
260
+ .gallery {
261
+ background: var(--panel); border: 1px solid var(--line);
262
+ border-radius: var(--radius); box-shadow: var(--shadow); position: relative;
263
+ max-height: var(--gallery-max, 560px); overflow-y: auto; overflow-x: hidden;
264
+ }
265
  .grid-head, .grow {
266
  display: grid;
267
  grid-template-columns: 52px minmax(200px, 1.3fr) 160px repeat(var(--ncol, 4), minmax(140px, 1fr));
 
585
  if (head) document.documentElement.style.setProperty('--head-h', head.offsetHeight + 'px');
586
  }
587
 
588
+ // Height of the gallery scroll box. HF auto-resizes the Space iframe to its
589
+ // full content height, so `window.innerHeight` / `vh` inside these nested
590
+ // iframes report the inflated content height, not the real viewport -- they
591
+ // can't be used to size a one-screen box. `screen.availHeight` is the screen
592
+ // work-area and is NOT affected by the iframe nesting, so we derive the box
593
+ // height from it (a fraction, clamped) and the rows scroll inside the box while
594
+ // the sticky header + ground-truth row stay locked.
595
+ // Reserve for everything that is NOT the scroll box but still has to fit on
596
+ // screen: the browser/OS chrome between the screen work-area and the window
597
+ // viewport, plus the HF page header + Gradio title/tabs + this page's caption
598
+ // and Refresh button. Subtracting it from the screen height keeps the whole
599
+ // gallery within one viewport, so there is a single scrollbar (the box's own)
600
+ // rather than the box plus an outer page scroll. Deliberately generous: a box
601
+ // that is a little short (a touch more in-box scrolling) is far better than a
602
+ // confusing second scrollbar.
603
+ var CHROME_RESERVE = 560;
604
+ function sizeGalleryBox() {
605
+ try {
606
+ const avail = (window.screen && window.screen.availHeight) || 900;
607
+ const h = Math.max(300, Math.min(760, Math.round(avail - CHROME_RESERVE)));
608
+ document.documentElement.style.setProperty('--gallery-max', h + 'px');
609
+ } catch (e) { /* keep CSS fallback */ }
610
+ }
611
+
612
+ // With the gallery box capped, the page content (caption + box) is bounded, so
613
+ // sizing the iframe to it keeps the iframe from adding a second scrollbar: the
614
+ // gallery's own box is the single scroller for the rows. No-ops if frameElement
615
+ // is unreadable.
616
  function fitIframe() {
617
  try {
618
  const fe = window.frameElement;
619
+ if (fe) fe.style.height = Math.ceil(document.body.scrollHeight) + 'px';
 
 
 
 
 
 
 
 
 
620
  } catch (e) { /* sandboxed -> keep fallback height */ }
621
  }
622
 
623
  buildGallery();
624
+ sizeGalleryBox();
625
  fitIframe();
626
+ function relayout() { syncHeadHeight(); sizeGalleryBox(); fitIframe(); }
627
  window.addEventListener('resize', relayout);
 
 
 
628
  if (window.ResizeObserver) new ResizeObserver(fitIframe).observe(document.body);
629
  if (document.fonts && document.fonts.ready) document.fonts.ready.then(relayout);
630
  """