gabriel / musehub public
Open #84 frontend
filed by gabriel human · 4 days ago · assigned to aaronrene

Fix two-column scroll layout: sticky sidebars clip and cannot scroll on all pages

0 Anchors
Blast radius
Churn 30d
0 Proposals

Problem

Any page with a sticky right-column sidebar clips its content and prevents scrolling. The repro in the issue detail view is the most visible: the CLOSE action in the right column is cut off and unreachable by scrolling.

This is a systemic failure, not a one-off bug. 11 page stylesheets share the same broken pattern.

Root causes

1. body { zoom: 1.25 } distorts 100vh

_layout.scss:40 sets body { zoom: 1.25 }. The CSS 100vh unit is the layout viewport height before zoom is applied, so max-height: calc(100vh - ...) on any zoomed page calculates against the wrong height. A sidebar that should be 800 px tall is computed as 1000 px, overflowing the visible area.

Fix: replace 100vh with 100dvh (dynamic viewport height, which respects zoom and browser chrome) across all affected files.

2. Grid parents missing min-height: 0

CSS grid items stretch to their intrinsic content height by default. Without min-height: 0 on the grid container, a sticky child's max-height has no scroll boundary to work against — the child just expands to full content height and the scrollbar never appears.

Affected layout classes: .isd-layout, .isl-layout, .proposal-detail-layout, and several others.

3. Inconsistent sticky top offset

Pages use at least four different expressions for the sticky top value:

  • var(--header-height) (repo-home, explore)
  • var(--sticky-offset, 80px) (proposal-detail, docs, blame, blob)
  • var(--header-height) + var(--space-4) (issues list sidebar)
  • hardcoded 80px (fallback scattered through fallback values)

When the repo tab strip is present --sticky-offset accounts for both header + tab strip height. When it is absent, pages use --header-height alone. The inconsistency means sidebars on tabbed pages start too high and get obscured by the tab strip.

Fix: always use var(--sticky-offset, var(--header-height)) as the canonical expression.

4. Missing mobile reset on several sidebars

_issues.scss correctly resets the sidebar on mobile (@media (max-width: 900px) { position: static; max-height: none; overflow-y: visible; }). Several other pages (repo-home, explore, muse-docs) do not include this reset, causing the sticky/scroll behaviour to break on narrow viewports.

Affected files (11 pages)

File Issues
pages/_issues.scss 100vh in two sidebars (list + detail); --header-height inconsistency
pages/_proposal-detail.scss 100vh; grid parent lacks min-height: 0
pages/_commit-detail.scss height: 100vh on grid container clips both columns
pages/_repo-home.scss 100vh; no mobile reset
pages/_explore.scss max-height: 100vh with no offset at all
pages/_docs.scss height: calc(100vh - ...)
pages/_muse-docs.scss 100vh fallback
pages/_blame.scss 100vh
pages/_blob.scss 100vh
pages/_profile.scss 100vh in graph sidebar
pages/_agents.scss overflow-y: auto with no height constraint
_layout.scss Root cause: body { zoom: 1.25 } + no --sticky-offset default

Canonical pattern (target state)

Every two-column page must use this exact shape:

// Grid wrapper — min-height: 0 is non-negotiable
.page-two-col {
  display: grid;
  grid-template-columns: 1fr 280px;
  align-items: start;
  min-height: 0;            // ← lets grid children shrink and scroll
  gap: var(--space-6);

  @media (max-width: 900px) { grid-template-columns: 1fr; }
}

// Sticky scrolling sidebar
.page-sidebar {
  position: sticky;
  top: var(--sticky-offset, var(--header-height));   // ← canonical offset expression
  max-height: calc(100dvh - var(--sticky-offset, var(--header-height)) - var(--space-4));
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--border-default) transparent;

  // Mobile reset — required on every sidebar
  @media (max-width: 900px) {
    position: static;
    max-height: none;
    overflow-y: visible;
  }
}

Key rules:

  • 100dvh not 100vh — everywhere, no exceptions
  • min-height: 0 on every grid/flex parent of a scrolling column
  • var(--sticky-offset, var(--header-height)) as the canonical sticky top expression
  • Mobile reset on every sticky sidebar

Shared layout utilities (target state)

Rather than duplicating this pattern in 11 files, extract two utility classes into _layout.scss:

.layout-two-col   // grid wrapper with min-height: 0 and responsive collapse
.layout-sidebar   // sticky sidebar with correct dvh, offset, and mobile reset

Pages import the utilities and add only page-specific overrides (column widths, gap).

Test plan (TDD — write tests first)

Phase 1 — Visual regression baseline

Capture before screenshots of every affected page at 1440×900 (desktop) and 390×844 (mobile). These are the reference images for regression detection in Phase 3.

Phase 2 — Unit tests: layout invariants

Write CSS/DOM tests (Vitest + jsdom or Playwright component tests) asserting:

  • SIDEBAR_01.layout-sidebar never overflows its grid parent at any viewport width
  • SIDEBAR_02.layout-sidebar is scrollable when its content exceeds max-height
  • SIDEBAR_03.layout-sidebar has position: static at width < 900px
  • SIDEBAR_04 — No element on any two-column page requires horizontal scrolling at 375px width
  • SIDEBAR_05 — All sticky elements have top equal to --sticky-offset (or --header-height on pages without repo tabs)
  • SIDEBAR_06 — No 100vh string appears in compiled CSS output (must be 100dvh)

Phase 3 — Integration: per-page scroll tests

Playwright tests for each of the 11 affected pages:

  • Load the page at 1440×900
  • Assert the sidebar is visible and its bottom edge is within the viewport
  • Scroll the sidebar content to the bottom
  • Assert the last child element of the sidebar is visible (not clipped)
  • Repeat at 390×844 — sidebar must reflow to static block

Phase 4 — Regression: before/after visual diff

Run the visual regression suite from Phase 1 against the fixed build. All diffs must be intentional (layout changes) with no unintended colour, spacing, or typography regressions.

Acceptance criteria

  • All 11 affected pages have a scrollable right sidebar that never clips content
  • The CLOSE action on the issue detail right column is reachable by scrolling at any viewport width ≥ 375px
  • No 100vh in compiled output — only 100dvh
  • min-height: 0 present on every two-column grid parent
  • .layout-two-col and .layout-sidebar utility classes extracted to _layout.scss
  • All 6 sidebar unit tests pass
  • All 11 per-page scroll integration tests pass
  • Mobile reflow (< 900px): all sidebars stack vertically with no scroll constraint
  • No horizontal overflow at 375px on any page

Out of scope

  • Redesigning the layout beyond the scroll/sticky fix
  • Dark/light theme changes
  • Any page not in the 11-file list above
Activity
gabriel opened this issue 4 days ago
No activity yet. Use the CLI to comment.