Label picker dropdown: rows misalign and wrap badly with multi-word label names and long descriptions #334

Closed
opened 2026-09-11 16:16:01 +00:00 by crueber · 3 comments
Owner

What's wrong

Since the default label packs landed (#324 — multi-word names like "good first issue" / "help wanted", plus long descriptions), the label picker dropdown renders poorly. Screenshot evidence:

  • Multi-word label names wrap to two lines ("good first / issue", "help / wanted"), which misaligns the color dots and the check column — rows become visually ragged.
  • Descriptions truncate abruptly mid-word ("This issue or pull r…", "Good for newc…", "Further information…") at inconsistent widths, and on wrapped rows the description starts on the second line at a different x-position than on single-line rows.
  • Rows have no consistent text grid: name and description share one flex line with no fixed columns, so every row lays out differently depending on name length.

Root cause (code evidence)

web/src/components/LabelPicker.jsx:108-127 — each row is one flat flex line:

<button class="flex w-full items-center gap-2 …">
  <span class="inline-block w-4 shrink-0 text-center">✓</span>       // check col
  <span class="… h-3 w-3 shrink-0 rounded-full …" />                  // color dot
  <span class="font-medium">{l.name}</span>                           // name — no shrink control, wraps freely
  <Show when={l.description}>
    <span class="muted truncate text-xs">{l.description}</span>       // truncate with no min-w-0/width constraint context
  </Show>
</button>

Problems: the name span has no whitespace-nowrap (wraps), the description truncate lacks a constrained flex context (min-w-0 chain) so it truncates arbitrarily, and name + description aren't in separate columns, so wrapped names push the description to a ragged position. The dropdown is fixed w-64 (:91) — fine, but the inner layout can't adapt.

Fix direction

Restructure the row into a three-column grid: [check+dot] [name] [description]:

  • Column 1: the existing check + color dot (shrink-0).
  • Column 2: label name with whitespace-nowrap — names never wrap; the dropdown width accommodates them (or the name truncates at an agreed limit with a title — but "good first issue" must fit; widen w-64 toward w-80 if needed).
  • Column 3: description on its own, right-aligned or left-aligned in a fixed-ratio column, truncate + min-w-0 so it truncates at a consistent width, with the full text in the row's title (already present — extend it to include the description if truncated).
  • Keep the menu semantics untouched: menuitemcheckbox, aria-checked, outside-click/Esc close, busy-disable, keyboard nav (none currently beyond Tab — unchanged).
  • Apply the same row anatomy anywhere else the pack labels render as picker rows if a second picker exists (the milestones page's milestone picker is a separate component — check it for the same layout fragility since milestone titles can also be multi-word, and note the finding in the PR either way).

Acceptance criteria

  • Multi-word label names ("good first issue", "help wanted") render on one line; color dots and checkmarks align in a consistent column.
  • Descriptions truncate at a consistent width with no mid-word raggedness; full description is reachable via the row tooltip.
  • Rows align to a stable grid — every row's name starts at the same x, every description occupies the same column region.
  • All existing picker behavior preserved: toggle apply/remove, aria-checked, busy-disable, outside-click and Esc close.
  • Light and dark themes; also verified at 390px mobile width (the picker lives in the issue sidebar).
  • Headless test update if the row structure is factored (per the headless-module convention); visual check in the PR.
## What's wrong Since the default label packs landed (#324 — multi-word names like "good first issue" / "help wanted", plus long descriptions), the label picker dropdown renders poorly. Screenshot evidence: - **Multi-word label names wrap to two lines** ("good first / issue", "help / wanted"), which misaligns the color dots and the check column — rows become visually ragged. - **Descriptions truncate abruptly mid-word** ("This issue or pull r…", "Good for newc…", "Further information…") at inconsistent widths, and on wrapped rows the description starts on the second line at a different x-position than on single-line rows. - Rows have **no consistent text grid**: name and description share one flex line with no fixed columns, so every row lays out differently depending on name length. ## Root cause (code evidence) `web/src/components/LabelPicker.jsx:108-127` — each row is one flat flex line: ```jsx <button class="flex w-full items-center gap-2 …"> <span class="inline-block w-4 shrink-0 text-center">✓</span> // check col <span class="… h-3 w-3 shrink-0 rounded-full …" /> // color dot <span class="font-medium">{l.name}</span> // name — no shrink control, wraps freely <Show when={l.description}> <span class="muted truncate text-xs">{l.description}</span> // truncate with no min-w-0/width constraint context </Show> </button> ``` Problems: the name span has no `whitespace-nowrap` (wraps), the description `truncate` lacks a constrained flex context (`min-w-0` chain) so it truncates arbitrarily, and name + description aren't in separate columns, so wrapped names push the description to a ragged position. The dropdown is fixed `w-64` (:91) — fine, but the inner layout can't adapt. ## Fix direction Restructure the row into a **three-column grid**: `[check+dot] [name] [description]`: - Column 1: the existing check + color dot (shrink-0). - Column 2: label name with `whitespace-nowrap` — names never wrap; the dropdown width accommodates them (or the name truncates at an agreed limit with a `title` — but "good first issue" must fit; widen `w-64` toward `w-80` if needed). - Column 3: description on its own, right-aligned or left-aligned in a fixed-ratio column, `truncate` + `min-w-0` so it truncates at a *consistent* width, with the full text in the row's `title` (already present — extend it to include the description if truncated). - Keep the menu semantics untouched: `menuitemcheckbox`, `aria-checked`, outside-click/Esc close, busy-disable, keyboard nav (none currently beyond Tab — unchanged). - Apply the same row anatomy anywhere else the pack labels render as picker rows if a second picker exists (the milestones page's milestone picker is a separate component — check it for the same layout fragility since milestone titles can also be multi-word, and note the finding in the PR either way). ## Acceptance criteria - [ ] Multi-word label names ("good first issue", "help wanted") render on one line; color dots and checkmarks align in a consistent column. - [ ] Descriptions truncate at a consistent width with no mid-word raggedness; full description is reachable via the row tooltip. - [ ] Rows align to a stable grid — every row's name starts at the same x, every description occupies the same column region. - [ ] All existing picker behavior preserved: toggle apply/remove, aria-checked, busy-disable, outside-click and Esc close. - [ ] Light and dark themes; also verified at 390px mobile width (the picker lives in the issue sidebar). - [ ] Headless test update if the row structure is factored (per the headless-module convention); visual check in the PR.
crueber added this to the v1 milestone 2026-09-11 16:16:01 +00:00
Author
Owner

Fix is up in #342 (#342, branch fix/issue-334): rows are now a three-column grid [check+dot] [name nowrap] [description truncate+min-w-0], panel w-64 → w-80. MilestonePicker checked — single-span rows, no sibling column to shift, left untouched. Full web suite 658 green, vite build green. Browser visual check (both themes, 390px) left for review — shared daemon blocks loopback. Not merging per workflow.

Fix is up in #342 (https://git.packden.us/crueber/walhub/pulls/342, branch fix/issue-334): rows are now a three-column grid [check+dot] [name nowrap] [description truncate+min-w-0], panel w-64 → w-80. MilestonePicker checked — single-span rows, no sibling column to shift, left untouched. Full web suite 658 green, vite build green. Browser visual check (both themes, 390px) left for review — shared daemon blocks loopback. Not merging per workflow.
Author
Owner

Review of PR #342 (535e09c, branch fix/issue-334) — verified in scratch worktree /tmp/pr342, main worktree untouched.

Grid anatomy — correct (LabelPicker.jsx:109-127). Col 1 groups check (w-4) + dot in one flex shrink-0 items-center gap-2 span with a single aria-hidden — constant-width column, a11y equivalent to before. Col 2 name is min-w-0 overflow-hidden text-ellipsis whitespace-nowrap font-medium — never wraps, ellipsizes past its half. Col 3 description is muted min-w-0 truncate text-xs — truncates at a consistent width. grid-cols-[auto_minmax(0,1fr)_minmax(0,1fr)] gives both text columns equal flexible halves, so every row's name starts at the same x. Rows without a description leave col 3 empty — grid position unchanged, no ragged shift.

w-80 fits at 390px — verified. 320px <= 390-16=374px (#278 max-width: calc(100vw - 1rem) cap in ui.css:181-183), so the right-anchored panel renders full-width with no shrink and no page-level horizontal scroll. Back-of-envelope on the name half: ~280px content minus ~44px col 1 leaves ~118px for the name; 'good first issue' at text-sm/medium is ~112px — fits, and any overlong name degrades to ellipsis + tooltip per the issue's agreed direction.

Title carries name + description (L109). Extends the pre-existing apply/remove + name title with — description only when present; rows without descriptions keep the old title byte-for-byte.

Semantics/keyboard unchanged. menuitemcheckbox, aria-checked, disabled=isBusy, onToggle, Esc + outside-click close, native buttons — all present, asserted in-test.

No #278 regression. label-drop scroll-slim card absolute right-0 z-30 hook intact; ui.css untouched — opaque rule (L170) and viewport bound (L181-183) still cover .label-drop; no new CSS (the grid-cols arbitrary value is a Tailwind-generated utility, confirmed present in shipped CSS: .grid-cols-[auto_minmax(0,1fr)_minmax(0,1fr)]{grid-template-columns:auto minmax(0,1fr) minmax(0,1fr)}), no new deps (diff touches 3 files only). Law 1 holds.

MilestonePicker out of scope — claim verified. MilestonePicker.jsx:93-111 rows are flex with check + a single title span (+ optional state chip), no description column; wrapped titles wrap inside one column and cannot ragged-shift a sibling. Left as-is is correct. Also checked LabelColorPicker.jsx:126 — a color-swatch listbox grid, not label rows; correctly untouched.

Dark + light. No color-class changes in the diff (shared card/hover/border treatment retained); layout-only change is theme-independent.

Doc decision accurate (02_issues.md). Grid shape, w-80 math, #278 hook, semantics, MilestonePicker note all match the code; it honestly flags browser proof as open rather than claiming it — correct, since I could not drive a browser either (no browser use per review instructions; node tests + reasoning only).

Tests (scratch worktree, node_modules symlinked from main, symlink died with the worktree): new label-picker-rows.test.js 7/7 pass; full suite node --test web/test/unit/*.test.js 658 pass / 0 fail (matches PR claim; suite takes ~119s — an earlier 180s-timeout run was just my limit, not a hang); vite build green with the arbitrary grid-cols rule confirmed generated into dist CSS.

No fixes needed — nothing pushed.

MERGE RECOMMENDATION: ready to merge (only remaining item is the live-browser visual check in both themes at 390px, which the doc already marks open for whoever has a browser handy).

Review of PR #342 (535e09c, branch fix/issue-334) — verified in scratch worktree /tmp/pr342, main worktree untouched. **Grid anatomy — correct (LabelPicker.jsx:109-127).** Col 1 groups check (w-4) + dot in one `flex shrink-0 items-center gap-2` span with a single aria-hidden — constant-width column, a11y equivalent to before. Col 2 name is `min-w-0 overflow-hidden text-ellipsis whitespace-nowrap font-medium` — never wraps, ellipsizes past its half. Col 3 description is `muted min-w-0 truncate text-xs` — truncates at a consistent width. `grid-cols-[auto_minmax(0,1fr)_minmax(0,1fr)]` gives both text columns equal flexible halves, so every row's name starts at the same x. Rows without a description leave col 3 empty — grid position unchanged, no ragged shift. **w-80 fits at 390px — verified.** 320px <= 390-16=374px (#278 `max-width: calc(100vw - 1rem)` cap in ui.css:181-183), so the right-anchored panel renders full-width with no shrink and no page-level horizontal scroll. Back-of-envelope on the name half: ~280px content minus ~44px col 1 leaves ~118px for the name; 'good first issue' at text-sm/medium is ~112px — fits, and any overlong name degrades to ellipsis + tooltip per the issue's agreed direction. **Title carries name + description (L109).** Extends the pre-existing apply/remove + name title with ` — description` only when present; rows without descriptions keep the old title byte-for-byte. **Semantics/keyboard unchanged.** menuitemcheckbox, aria-checked, disabled=isBusy, onToggle, Esc + outside-click close, native buttons — all present, asserted in-test. **No #278 regression.** `label-drop scroll-slim card absolute right-0 z-30` hook intact; ui.css untouched — opaque rule (L170) and viewport bound (L181-183) still cover .label-drop; no new CSS (the grid-cols arbitrary value is a Tailwind-generated utility, confirmed present in shipped CSS: `.grid-cols-[auto_minmax(0,1fr)_minmax(0,1fr)]{grid-template-columns:auto minmax(0,1fr) minmax(0,1fr)}`), no new deps (diff touches 3 files only). Law 1 holds. **MilestonePicker out of scope — claim verified.** MilestonePicker.jsx:93-111 rows are flex with check + a single title span (+ optional state chip), no description column; wrapped titles wrap inside one column and cannot ragged-shift a sibling. Left as-is is correct. Also checked LabelColorPicker.jsx:126 — a color-swatch listbox grid, not label rows; correctly untouched. **Dark + light.** No color-class changes in the diff (shared card/hover/border treatment retained); layout-only change is theme-independent. **Doc decision accurate (02_issues.md).** Grid shape, w-80 math, #278 hook, semantics, MilestonePicker note all match the code; it honestly flags browser proof as open rather than claiming it — correct, since I could not drive a browser either (no browser use per review instructions; node tests + reasoning only). **Tests (scratch worktree, node_modules symlinked from main, symlink died with the worktree):** new label-picker-rows.test.js 7/7 pass; full suite `node --test web/test/unit/*.test.js` 658 pass / 0 fail (matches PR claim; suite takes ~119s — an earlier 180s-timeout run was just my limit, not a hang); `vite build` green with the arbitrary grid-cols rule confirmed generated into dist CSS. No fixes needed — nothing pushed. **MERGE RECOMMENDATION: ready to merge** (only remaining item is the live-browser visual check in both themes at 390px, which the doc already marks open for whoever has a browser handy).
Author
Owner

Fixed by PR #342 (review clean; grid anatomy + 390px fit + #278 no-regression + shipped-CSS verified; 658/658), merged. Closing.

Fixed by PR #342 (review clean; grid anatomy + 390px fit + #278 no-regression + shipped-CSS verified; 658/658), merged. Closing.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
crueber/walhub#334
No description provided.