← reports

#2551 · Project picker dropdown does not scroll: projects past the window bottom are unreachable

Bug Priority: not set Effort: not set ui open on GitHub 2026-08-27 · base ad79bbb5ec909524f8f281e62d860c588a86f332

Verdict: REPRODUCED · Root-cause confidence: high

1. TL;DR

The desktop project picker puts every project in one menu without a height limit.

With 30 projects, the menu bottom reached 1,101 px in a 650 px viewport.

The shared desktop menu hides overflow, and ProjectSelector adds no height limit or scroll behavior.

Radix supplies the correct 426 px available height, so a small local class change makes every project reachable.

2. Claims vs findings

Claim from the issueStatusEvidence
The menu runs below the window edge. Verified The menu bottom was 1,101 px. The viewport bottom was 650 px.
The menu has no scrollbar, and wheel input does not move it. Verified Computed overflow-y was hidden. A 700 px wheel input left scrollTop at zero.
Projects below the edge cannot be read or selected. Verified The last item occupied y=1,070–1,096 px. The menu had no scroll range.
A short project list fits and does not show the fault. Verified A measured five-project menu was 227 px high and ended at y=451 px. It fit in the 650 px viewport.
The exact report came from the latest macOS desktop release. Unverified The issue does not record a version. This check used the same app frontend on Linux Chrome.

3. Environment

4. Minimal reproduction

  1. Check out the tested commit.
    git checkout --detach ad79bbb5ec909524f8f281e62d860c588a86f332
    pnpm install --frozen-lockfile --prefer-offline
    pnpm exec turbo run build
  2. Start an isolated development instance.
    scripts/bb-dev-app current
    eval "$(scripts/bb-dev-app env)"
  3. Create 30 local project fixtures.
    host_id=$(node packages/scripts/dist/commands/run-cli.js machine list --json | jq -r '.[0].id')
    scratch_root=$(mktemp -d /tmp/bb-2551-repro-XXXXXX)
    
    for project_num in $(seq -w 1 30); do
      project_path="$scratch_root/project-$project_num"
      mkdir -p "$project_path"
      git -C "$project_path" init -q
      curl -sS -X POST "$BB_SERVER_URL/api/v1/projects" \
        -H 'content-type: application/json' \
        -d "{\"name\":\"QA Project $project_num\",\"source\":{\"type\":\"local_path\",\"path\":\"$project_path\",\"hostId\":\"$host_id\"}}" \
        >/dev/null
    done
    
    curl -sS "$BB_SERVER_URL/api/v1/projects" \
      | jq '{projectCount: length, firstProject: .[0].name, lastProject: .[-1].name}'

    The corrected direct CLI command returned clean JSON. The fixture command completed with this output.

    {
      "projectCount": 30,
      "firstProject": "QA Project 01",
      "lastProject": "QA Project 30"
    }
  4. Open the app URL in a 1,100 × 650 window.
  5. Click Work in a project.
  6. Move the pointer over the menu and scroll down.

Expected output

menu bottom: <= 650px
overflow-y: auto
scrollTop after wheel: > 0
last item bottom: <= 650px after scroll

Actual output

fixture project count: 30
viewport height: 650px
available height variable: 426px
menu top / bottom / height: 224px / 1101px / 877px
overflow-y: hidden
max-height: none
scrollTop before wheel: 0
scrollTop after 700px wheel: 0
last item top / bottom: 1070px / 1096px

Short-list control

I repeated the browser measurement with five projects.

fixture project count: 5
viewport height: 650px
menu top / bottom / height: 224px / 451px / 227px
last item top / bottom: 420px / 446px

The short list fit inside the viewport. It did not show the reported fault.

The closed project picker in the 1100 by 650 test window
The closed picker shows the exact control that starts the fault.
The open project menu continues below the viewport
The open menu reaches the window edge at project 15. Seventeen later actions remain below the edge.

Regression test

Save the test file as apps/app/src/components/pickers/ProjectSelector.issue2551.test.tsx.

// @vitest-environment jsdom

import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { ProjectSelector } from "./ProjectSelector";

afterEach(cleanup);

describe("ProjectSelector issue #2551 reproduction", () => {
  it("caps the desktop menu to its available height and lets it scroll", () => {
    const projects = Array.from({ length: 30 }, (_, index) => ({
      id: `proj_${index + 1}`,
      name: `QA Project ${String(index + 1).padStart(2, "0")}`,
    }));

    render(
      <ProjectSelector
        projects={projects}
        value={null}
        onChange={() => undefined}
        allowNoProject
        defaultOpen
        modal={false}
      />,
    );

    const menu = screen.getByRole("menu");
    expect(menu.className).toContain(
      "max-h-[var(--radix-dropdown-menu-content-available-height)]",
    );
    expect(menu.className).toContain("overflow-y-auto");
  });
});

Run this command from apps/app.

pnpm exec vitest run src/components/pickers/ProjectSelector.issue2551.test.tsx

The base test fails at the first class assertion. See the full base output.

5. Root cause

ProjectSelector gives the content only a width class. It then renders every project into that content.

<DropdownMenuContent align="start" side="bottom" className="w-52">
  <DropdownMenuLabel>Project</DropdownMenuLabel>
  {projects.map((project) => (
    <DropdownMenuItem key={project.id} ...>
      ...
    </DropdownMenuItem>
  ))}

See ProjectSelector.tsx lines 144–166.

The shared desktop dropdown adds overflow-hidden. It does not add a height limit or a vertical scroll region.

className={cn(
  "z-50 min-w-28 overflow-hidden rounded-md border ...",
  className,
)}

See dropdown-menu.tsx lines 187–213.

Radix measured 426 px of available height and set its CSS variable. No project-picker class consumes that variable.

The menu therefore keeps its full 877 px height. The window clips 451 px, and hidden overflow prevents internal movement.

The compact branch already adds overflow-y-auto inside its responsive drawer.

See the compact dropdown branch.

git blame traces the width-only project menu to commit cebef078d.

origin/main advanced past the base commit. No later commit changed either source path.

6. Proposed fix (first principles)

Give the desktop project menu a maximum height from Radix. Let the same element own vertical scroll.

<DropdownMenuContent
  align="start"
  side="bottom"
  className="max-h-[var(--radix-dropdown-menu-content-available-height)] w-52 overflow-y-auto overscroll-contain"
>

This local change preserved the menu structure. It changed the measured menu height from 877 px to 426 px.

The same wheel input changed scrollTop from zero to 451. The final item then ended at 645 px.

The regression test passed with this change. See the passing test output.

The proposed scroll classes expose projects 18 through 30 after wheel input
The proposed classes keep the menu inside the window. A wheel action exposes the final project and both actions.

A production change should also test the compact drawer. That test will prevent a desktop height rule from limiting the drawer.

Keep the change in the app layer. This fault does not change a server or host-daemon contract.

7. Related issues

8. Appendix

Evidence files

Commands and checks

gh issue view 2551 --comments
gh issue view 2551 --json number,title,body,labels,projectItems,state,url,comments
git fetch origin main
git rev-parse HEAD origin/main
git log ad79bbb5ec90..origin/main -- apps/app/src/components/pickers/ProjectSelector.tsx
git log ad79bbb5ec90..origin/main -- packages/shared-ui/src/components/ui/dropdown-menu.tsx
git blame ad79bbb5ec90 -L 141,145 -- apps/app/src/components/pickers/ProjectSelector.tsx
git blame ad79bbb5ec90 -L 187,213 -- packages/shared-ui/src/components/ui/dropdown-menu.tsx
rg -n "Work in a project|ProjectSelector" apps packages plugins
scripts/bb-dev-app current
eval "$(scripts/bb-dev-app env)"
node packages/scripts/dist/commands/run-cli.js machine list --json
node packages/scripts/dist/commands/run-cli.js provider list --json
curl -sS "$BB_SERVER_URL/api/v1/projects"
doobie --headless -b bb-report-2551 ...
pnpm exec vitest run src/components/pickers/ProjectSelector.issue2551.test.tsx

The locked install passed. The Turbo build completed 18 tasks.

The direct CLI returned clean machine JSON and created all 30 project fixtures.

The base regression test failed, and the proposed-fix test passed.

No open pull request links to this issue. I did not review a pull request.

Verification

The verifier first found that the documented wrapper added text before its JSON output.

I replaced that wrapper with the built direct CLI and created 30 projects successfully.

I repeated the long-list browser check. The 877 px base menu reached y=1,101 px and did not scroll.

I also measured a five-project control. Its 227 px menu ended at y=451 px and fit in the viewport.

I reran the saved test. It failed on the base code and passed with the proposed classes.

I also removed the unsupported priority value and recorded the current origin/main result.