fix: let the session list scroll instead of cutting off the footer

Merged
DA
2mo ago
+61-28

Summary

  • Make the panel body between the header and footer the one scroll container in the app, so a long session list scrolls instead of pushing the footer out of a fixed-size panel.
  • Keep the header and footer out of that scroll region, so microphone state and the controls that act on it stay on screen at any session count.
  • Give session rows their natural height instead of sharing the panel between them.

The bug

The expanded panel is a fixed 620x520 box with overflow: hidden. The session list grows with however many sessions a developer is running, and past four it pushed the footer out of the panel. Nothing could scroll it back: the root is deliberately overflow: clip, because hidden would leave a scroll container that a focus or scrollIntoView can use to drag the notch capsule out of view while compact.

Five observed sessions was enough to put the microphone permission readout, "Start microphone", and "Quit" out of reach — well within what Luke is for.

Why the body and not the root

overflow: clip on the root is load-bearing and stays. The new scroll container is scoped to the panel body and carries overscroll-behavior: contain, so it cannot chain a scroll up to the root and reintroduce the problem that comment is guarding against. min-height: 0 is what actually lets it scroll — a flex child defaults to its content size and would otherwise keep pushing the footer down.

Evidence

  • Platform-independent checks: ./scripts/check.sh passed — lint, 3 typecheck projects, 62 tests (5 harness, 31 core, 26 desktop), all builds
  • macOS Electron verification (./scripts/verify.sh): not run — this Linux workspace cannot package the macOS app; CI's macOS app/evidence job covers it
  • Layout measured against the real styles.css at 620x520 in headless Chrome, before and after, across session counts. footerCutOffBy is how far the footer sits below the panel's bottom edge in pixels; lastRowReachable scrolls the body to its end first and then asks whether the final row is in view.

Before:

sessions= 3: footerCutOffBy=0    startReachable=true
sessions= 4: footerCutOffBy=0    startReachable=true
sessions= 5: footerCutOffBy=60   startReachable=false
sessions= 6: footerCutOffBy=130  startReachable=false
sessions= 8: footerCutOffBy=270  startReachable=false
sessions=12: footerCutOffBy=550  startReachable=false

After:

sessions= 3: footerCutOffBy=0 startReachable=true scrollable=false lastRowReachable=true
sessions= 4: footerCutOffBy=0 startReachable=true scrollable=true  lastRowReachable=true
sessions= 5: footerCutOffBy=0 startReachable=true scrollable=true  lastRowReachable=true
sessions= 6: footerCutOffBy=0 startReachable=true scrollable=true  lastRowReachable=true
sessions= 8: footerCutOffBy=0 startReachable=true scrollable=true  lastRowReachable=true
sessions=12: footerCutOffBy=0 startReachable=true scrollable=true  lastRowReachable=true

That measurement is Chrome rendering the real stylesheet, not the packaged Electron app. It establishes the CSS math and the regression threshold; it does not replace looking at the app.

Automated visual evidence

Download the deterministic macOS evidence · workflow run

  • Commit: 20bb11e448cb6adcabeed569349e74546c693d6a
  • Scenario: smoke
  • Physical-notch check: not performed by CI

Physical-device evidence

  • Screenshot or screen recording: not attached
  • Physical-notch check: not performed
  • Device/display configuration: not recorded

Notes

  • The fixture scenario has three sessions, so the deterministic evidence screenshots are unchanged by this fix. The CI PNGs are worth an eye anyway, since the flex changes touch how rows are sized at counts the fixture does not cover.
  • This is a precursor to #12, which adds a voice panel below the session list and hits the same ceiling much sooner.

🤖 Generated with Claude Code


Open workspace in Conductor