Skip to content

fix: dual-view dropdown columns overflowed the panel (#258 regression) - #263

Merged
BernardJen merged 1 commit into
mainfrom
fix/dual-dropdown-fit
Aug 19, 2026
Merged

fix: dual-view dropdown columns overflowed the panel (#258 regression)#263
BernardJen merged 1 commit into
mainfrom
fix/dual-dropdown-fit

Conversation

@BernardJen

Copy link
Copy Markdown
Contributor

Reported: the dropdown items do not fit in dual view. They did not — and the cause
was two separate mistakes in #258, one of them worse than a layout problem.

The overflow

Giving each input row a key chip meant giving the name white-space: nowrap so it
would ellipsise rather than wrap. .column-layout uses
grid-template-columns: 1fr 1fr, and 1fr is minmax(auto, 1fr) whose auto
minimum is the item's min-content size. With wrapping text that is the longest
word; with nowrap it is the entire label.

Measured before the fix:

grid tracks 339.758px 339.758px
container 358px
overflow ~320px out of the dropdown

Fixed with minmax(0, 1fr). This is the same trap as flex min-width: auto, one
level up — and the flex one was already fixed in this very file, on the name
span, which is exactly how the grid one got missed.

The chip should not have been in the dual columns at all

Checking why the columns were cramped turned up something worse:

  • 14 call selectInput() with the default side='both' → sets both feeds
  • clicking a row in the Left column calls selectInputForSide(id, 'left') → sets one

So the chip documented a key that does something different from the control beside
it. Chips now render on the single-view list only, where one feed is shown and
setting both is the same thing to the operator as setting that one.

That resolves the fit on its own terms too. A dual column is ~173px against the
single list's ~358px; a name plus a chip left about 98px for the name, which turns
Blackmagic UltraStudio Recorder 3G into Blackmagic Ul… — useless for telling
two capture cards apart. Name truncation is now scoped to the list that has a chip,
so dual-column names wrap again exactly as they did before #258.

Measured after

Dual view, 400px panel:

  • tracks 173px + 173px + 12px gap against a 358px container — fits
  • no chips in the columns
  • every name fully visible, nothing truncated
  • panel inside the viewport

Single view: 14 chips present, 322px rows, uniform row heights, and the long
Blackmagic label is not even truncated at that width.

Tests

Two new, 557 → 559. They pin both halves of the fix, because either alone would
have hidden the other:

Mutation Caught
grid back to bare 1fr 1fr
nowrap re-applied to the dual columns
chip put back on the dual columns

jsdom does no layout, so the CSS assertions check the declaration rather than
measuring — the measurements above came from a real browser.

On how this got shipped

My verification of #258 screenshotted the dropdown in Single mode only. The
two-column layout is the one that breaks, and I never looked at it. The mode was
one click away and I had the browser open.

Reported as the dropdown items not fitting in dual view. They did not, and the
cause turned out to be two separate mistakes in #258 -- one layout, one worse
than layout.

**The overflow.** Adding a key chip to each input row meant giving the name
`white-space: nowrap` so it would ellipsise rather than wrap. `.column-layout`
uses `grid-template-columns: 1fr 1fr`, and `1fr` is `minmax(auto, 1fr)` whose auto
minimum is the item's MIN-CONTENT size. With wrapping text that is the longest
word; with nowrap it is the entire label. So the tracks computed to 339.758px each
inside a 358px panel and the columns spilled roughly 320px out of the dropdown.

That is the same trap as flex `min-width: auto`, one level up -- and the flex one
was already fixed in this very file, on the name span, which is how the grid one
got missed. Fixed with `minmax(0, 1fr)`.

**The chip should not have been there at all.** `1`-`4` call selectInput() with
the default side='both' and set BOTH feeds. Clicking a row in the Left column
calls selectInputForSide(id, 'left') and sets one. So a chip on a per-side row
documented a key that does something different from the control it sat next to.

Chips now render on the single-view list only, where one feed is shown and setting
both is the same thing to the operator as setting that one. That also resolves the
fit on its own terms: a dual column is ~173px against the single list's ~358px, and
a name plus a chip left about 98px for the name -- enough to turn "Blackmagic
UltraStudio Recorder 3G" into "Blackmagic Ul...", which is useless for telling two
capture cards apart.

Name truncation is scoped to the single list with the chip. Dual-column names wrap
again, exactly as they did before #258.

Measured after the fix, dual view at a 400px panel: tracks 173px + 173px + 12px gap
against a 358px container, no chips in the columns, every name fully visible with
nothing truncated, panel inside the viewport. Single view keeps its 1-4 chips at
322px rows, where the long Blackmagic label is not even truncated.

Two new tests, 557 -> 559, both pinning halves of the fix that would each have
hidden the other: that the grid uses minmax(0, 1fr) and not a bare 1fr, and that
nowrap is scoped to the list that has a chip. Verified by reverting each in turn,
plus putting the chip back on the dual columns.
@BernardJen
BernardJen merged commit 4bdddac into main Aug 19, 2026
1 check passed
@BernardJen
BernardJen deleted the fix/dual-dropdown-fit branch August 19, 2026 22:03
BernardJen added a commit that referenced this pull request Aug 20, 2026
The mirror of the input dropdown, anchored to the bottom edge instead of the top:
same hover-to-reveal, same 0.3s slide, same touch-open class, same cursor
handling, corners rounded on the side away from the edge it hangs off.

Rows come from shortcuts.js, which makes this the third consumer of that list
after the keydown handler and the Settings table (#258). It shows the SAME rows as
the table rather than a shortened "important ones" set -- that would have been a
fourth hand-maintained list, which is the thing that list exists to remove. A test
asserts the two carry identical chips.

Wider than the dropdown on purpose. The dropdown is a picker capped at 400px; this
is a reference table and the wall is 5:1, so an auto-fit grid turns twelve rows
into three rows of four columns instead of one column twelve deep. Measured at a
1500px viewport: 1100x183, four 244px tracks, sitting on the bottom edge, no
scrolling.

Two things measuring caught that reasoning would not have:

**A fixed-position element is shrink-to-fit, so `max-width` caps it but never
expands it** -- and `repeat(auto-fit, minmax(...))` needs a DEFINITE width to work
out its column count. With only a max the panel sized to its content minimum: 467px
wide and one column at 1500px, which is precisely the long-list-off-the-bottom the
grid was there to prevent. Fixed with an explicit `width: min(1100px, 92vw)`.

**Three of twelve labels were ellipsising at 1100px**, including both
"(if the remote keyboard is enabled)" caveats -- the part that makes those two rows
mean anything. Labels now wrap. Uneven row heights are the cheaper cost; a legend
that hides what a key does defeats itself. Same trade as the dual-column names in
#263.

Escape closes it, alongside the other panels.

13 new tests, 582 -> 595. Verified by breaking each: dropping the explicit width
fails 1, truncating labels 1, removing the Escape close 1, and rendering a
shortened set 4 -- including the test that pins the legend and the Settings table
to identical chips.

One note on verification method. The Browser pane reports document.hidden: true and
never goes visible, so the transition clock is frozen and getComputedStyle returns
the pre-transition value indefinitely. Reading the panel's transform after a toggle
showed translateY(0) in both states, which looked like a broken rule and was not --
it was the same trap as #262. Disabling the transition makes the change land in one
style recalculation, which is readable regardless: closed translateY(183) fully
off-screen, open translateY(0) on the bottom edge, trigger 0 -> 0.7 with the
cursor.
BernardJen added a commit that referenced this pull request Aug 20, 2026
The mirror of the input dropdown, anchored to the bottom edge instead of the top:
same hover-to-reveal, same 0.3s slide, same touch-open class, same cursor
handling, corners rounded on the side away from the edge it hangs off.

Rows come from shortcuts.js, which makes this the third consumer of that list
after the keydown handler and the Settings table (#258). It shows the SAME rows as
the table rather than a shortened "important ones" set -- that would have been a
fourth hand-maintained list, which is the thing that list exists to remove. A test
asserts the two carry identical chips.

Wider than the dropdown on purpose. The dropdown is a picker capped at 400px; this
is a reference table and the wall is 5:1, so an auto-fit grid turns twelve rows
into three rows of four columns instead of one column twelve deep. Measured at a
1500px viewport: 1100x183, four 244px tracks, sitting on the bottom edge, no
scrolling.

Two things measuring caught that reasoning would not have:

**A fixed-position element is shrink-to-fit, so `max-width` caps it but never
expands it** -- and `repeat(auto-fit, minmax(...))` needs a DEFINITE width to work
out its column count. With only a max the panel sized to its content minimum: 467px
wide and one column at 1500px, which is precisely the long-list-off-the-bottom the
grid was there to prevent. Fixed with an explicit `width: min(1100px, 92vw)`.

**Three of twelve labels were ellipsising at 1100px**, including both
"(if the remote keyboard is enabled)" caveats -- the part that makes those two rows
mean anything. Labels now wrap. Uneven row heights are the cheaper cost; a legend
that hides what a key does defeats itself. Same trade as the dual-column names in
#263.

Escape closes it, alongside the other panels.

13 new tests, 582 -> 595. Verified by breaking each: dropping the explicit width
fails 1, truncating labels 1, removing the Escape close 1, and rendering a
shortened set 4 -- including the test that pins the legend and the Settings table
to identical chips.

One note on verification method. The Browser pane reports document.hidden: true and
never goes visible, so the transition clock is frozen and getComputedStyle returns
the pre-transition value indefinitely. Reading the panel's transform after a toggle
showed translateY(0) in both states, which looked like a broken rule and was not --
it was the same trap as #262. Disabling the transition makes the change land in one
style recalculation, which is readable regardless: closed translateY(183) fully
off-screen, open translateY(0) on the bottom edge, trigger 0 -> 0.7 with the
cursor.
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