Skip to content

Ringside: replace native selects with a downward-opening listbox#54

Open
mlava wants to merge 1 commit into
NateBJones-Projects:mainfrom
mlava:fix/ringside-run-menu
Open

Ringside: replace native selects with a downward-opening listbox#54
mlava wants to merge 1 commit into
NateBJones-Projects:mainfrom
mlava:fix/ringside-run-menu

Conversation

@mlava

@mlava mlava commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Problem

The run picker and version picker in Ringside are native <select> elements sitting at the top of the page. macOS anchors a native select popup so the checked option sits over the control, which means a long run list extends upward — off the top of the screen, clipped and unreachable:

The popup direction of a native select is OS-controlled: no CSS or JS can redirect it, and the CSS customizable-select path (appearance: base-select) is Chromium-only — Ringside is viewed in Safari and VS Code Simple Browser (WebKit), so it never applies.

Fix

Both pickers are now a small vanilla listbox component (createDropdown: button trigger + role="listbox" panel) that always opens downward:

  • Panel is capped to the viewport space below the trigger, scrolls internally, and scrolls the checked option into view on open.
  • Keyboard parity with the native control: ArrowUp/Down open and navigate, Home/End, Enter/Space select, Escape closes, type-ahead jumps by run name.
  • ARIA wiring: aria-haspopup/aria-expanded on the trigger, aria-activedescendant + aria-selected on the listbox.
  • The version picker right-aligns its panel when it would clip the right viewport edge.
  • Live-poll safe: the 1s sync() re-renders mutate options in place, so an open menu doesn't lose its active row or scroll position.
  • The syncSelect helper is replaced by the component's sync(); change handlers moved to pickArtifact()/pickVersion() with identical behavior.
  • Bonus: restores the chevron affordance on the pickers — the background: shorthand in .artifact-tools was silently wiping the background-image chevron declared earlier in the sheet.

The inject_models_tab_into_ringside_html anchor lines in ringer.py (main {, <main>, </main>, tickClock();) are untouched; injection re-tested against the new file.

Verification

Playwright against a live Ringside with 26 real runs, plus a stubbed-API run of the merged file:

  • Menu geometry asserted: panel top ≥ trigger bottom, panel bottom ≤ viewport (1280×800 and 900×420).
  • Selecting a run swaps the artifact frame; menu closes; trigger label updates.
  • Keyboard open/navigate/select/dismiss all pass; zero page errors.
  • Dark and light themes render correctly; models-tab injection verified intact.

🤖 Generated with Claude Code

The run picker and version picker were native <select> elements; macOS
anchors that popup so the checked option sits over the control, which
sends long run lists off the top of the screen (the pickers live at the
top of the page, so upward overflow is always clipped). The popup
direction of a native select is OS-controlled and cannot be fixed with
CSS, and the customizable-select CSS (appearance: base-select) is not
available in WebKit, which Ringside runs in via VS Code Simple Browser
and Safari.

Both pickers are now a small custom listbox (button trigger +
role="listbox" panel) that always opens downward, capped to the
viewport space below the trigger with internal scrolling and the
selected option scrolled into view. Keyboard support matches the native
control: ArrowUp/Down to open and move, Home/End, Enter/Space to
select, Escape to close, type-ahead by option text. The
version picker right-aligns its panel when it would overflow the right
viewport edge. Also restores the chevron affordance that the
`background:` shorthand in .artifact-tools had been silently wiping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant