* feat(web): surface PR mergeability + conflict state in the CI popover
The CI hover popover showed checks/reviews but nothing about mergeability, so an
unmergeable PR silently dropped its merge button with no reason. Add a mergeability
row that reuses the detail panel's conflict banner + "Resolve conflicts" agent prompt
for dirty PRs, a "Behind base" chip, and a branch-protection note explaining why a
blocked PR is gated. Replace the compact merge split-button+dropdown with an inline
primary + "or <method>" buttons so the menu's detached portal no longer closes the
hover popover.
* feat(web): reflect PR mergeability in status colors and badges
The PR status pill, badge, and chat-input CI chip derived their colour from
checks/reviews only, so a conflicted or blocked PR still read as a green "all good"
check. Map dirty -> red, behind -> amber, and branch-protection blocked -> amber across
getPRStatusColor, the PRStatusIcon badge, and the chip glyph, so the indicators agree
with the popover. Blocked stays sky/in-progress while a requested review is still
outstanding (that's the awaiting-review gate, not a hard block).
* refactor(web): remove dead merge-menu keep-open plumbing
The compact merge control was redesigned to inline buttons (no dropdown portal),
which already fixes the hover popover closing. That leaves the keepOpen ref /
setKeepOpen / onMergeMenuOpenChange / onMenuOpenChange chain threaded through
usePopoverInteractions, PRCIPopover, and PRMergeButton unreachable: the popover
always renders PRMergeButton with compact, and the only DropdownMenu lives in the
non-compact detail-panel path that never passes the callback. Drop the unused
scaffolding (flagged by OpenCode + greptile).
* fix(web): keep CI popover quiet for blocked PRs only awaiting review
PRMergeabilityRow rendered "Blocked by branch protection" for any blocked PR,
including one blocked solely because a requested reviewer hasn't responded. That
contradicted the chip/pill/badge, which deliberately read that case as the calm
"awaiting review" state. Skip the note (render nothing) when isPRAwaitingReview is
true, and narrow blockedReason so an empty checks_state no longer falsely claims a
required status check failed. Flagged by Claude (blocker) and greptile.
* refactor(web): rename mergeability-notice compact prop to popover
The `compact` prop made the chip/text larger (to match the popover's text-xs rows)
while ConflictBanner's `compact` tightened padding — so "compact" rendered bigger,
which reads as inverted. Rename it to `popover` (the variant rendered inside the CI
hover popover) and document that the chip intentionally matches the popover rows.
No visual change. Flagged by cubic.
* fix(web): show default cursor on disabled compact merge alternates
The compact "or <method>" alternates kept cursor-pointer while disabled (during the
~1s merging state); add disabled:cursor-default to match the primary button. Flagged
by OpenCode.
* feat(web): tabbed multi-PR CI popover for topbar button and chat status chip
* test(e2e): cover multi-PR CI popover tabs, chip aggregate, and dropdown
* fix(web): address multi-PR popover review feedback
- suppress hover popover while the PR dropdown is open (no stacked overlays)
- repo-scope tab testids so same-number PRs on different repos stay unique
- guard non-array taskPRs store value in PRStatusChip (same as PRTaskIcon)
- add tablist/tab/aria-selected semantics to the popover tabs
* fix(web): warm default PR only, skip terminal PRs, add tabpanel ARIA
- chip background-sync warms pickDefaultPR (the tab the popover opens), not list head
- PRFeedbackWarmer skips merged/closed PRs (no wasted fetches in topbar path)
- tabpanel role + aria-controls/labelledby wire tabs to the panel
* fix(web): complete ARIA tabs keyboard model in multi-PR popover
- roving tabindex: only the active tab in the page tab order
- ArrowLeft/ArrowRight move selection + focus within the tablist (wrapping)
- stable useId-scoped tabpanel id so every tab's aria-controls resolves
- document topbar-only scoping of prCheckGroup e2e helpers
* refactor(web): skip duplicate rank call for first PR in pickDefaultPR
* fix(web): make PR CI status reachable on mobile via tap-activated drawer
PRStatusChip's HoverCard was hover-only, so touch users in the chat
status bar had no way to open the CI status surface. Branch on
useIsMobile(): keep the existing HoverCard on desktop, and on mobile
render a vaul Drawer hosting the same PRCIPopover body so tapping the
chip opens a bottom-sheet "modal" with passes / failures / reviews.
The desktop topbar PR button isn't mounted in SessionMobileTopBar (and
its click path is Dockview-only), so the chip is the only surface that
needs the fix today. New mobile-chrome e2e spec covers tap-to-open,
failed-bucket content, and close.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs(e2e): rephrase PR CI accessors section comment
The block now spans desktop hover popover + chip + mobile drawer
locators, not just the hover popover.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(web): cover PRStatusChip desktop/mobile branching in jsdom
Adds focused unit tests around the new mobile drawer path:
- desktop branch renders a chip and click does NOT mount the drawer
- mobile branch starts with no drawer, click mounts drawer + popover
inner + close button (regression guard for the e2e's toHaveCount(0)
precondition)
- chip data-* attrs preserved across both branches
- failed PR maps to data-status='failed'
- no-checks PR shows the popover's empty state inside the drawer
Close-on-dismiss can only be verified in a real browser (happy-dom
doesn't fire vaul's CSS transition events) — covered by the
mobile-pr-ci-chip.spec.ts e2e.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(web): a11y polish on mobile PR/CI chip drawer trigger
Addresses bot review feedback on #1060:
- Add aria-haspopup="dialog" + aria-expanded={open} on the mobile chip
button (mirrors the established mobile-pill-button.tsx pattern).
- Add a visually hidden DrawerDescription to satisfy Radix's accessible
Dialog contract and suppress the console warning observed during
unit tests.
- Annotate the e2e failed-bucket count assertion with the arithmetic
(3 total - 2 passing = 1 failed) so future seed-value edits stay
in sync.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>