feat(desktop): peek an issue beside the Issues board instead of leaving it

Merged
DA
11d ago
+1032-158

Part of the "Improve Issues board" plan, item "Peek, don't leave": open an issue beside the board or list instead of in its place, so triage keeps its place.

Behavior change

A press or Return on a card or list row now opens a peek of the issue. It no longer replaces the board with the issue's page. The issue's page is still a ⌘ away:

GestureBeforeNow
Press / Return on a card or rowOpens the issue's pageOpens the peek beside the board or list
⌘-press / ⌘Return(same as press)Opens the issue's page
Peek's "Open as page" button—Opens the issue's page
EscapeLeaves Issues (on the board) / back to board (on a page)Closes the peek first. The next Escape works as before.
Luke link, ⌘K, Back/ForwardOpens the issue's pageUnchanged

In a window too narrow for the peek at its minimum width beside one board column, a press opens the issue's page as it always did.

What it does

  • A pane of the Issues page, not a side-panel tab. The window's side panel holds a task's tabs: they are kept, ordered and pinned per task. An issue has no task. The automation's Runs pane is the precedent this follows. The peek docks at the page's right on usePaneSlide. Its left edge is the side panel's resize edge (useResizableEdge): it resizes between 360 and 720px, the width is kept on this Mac, and dragging past the minimum closes the peek. The page's PaneGlide moves the toolbar and the list as the pane takes or gives back its room. No width is ever animated.
  • The peek follows the keyboard. While a peek is open, ↑ ↓ ← → (and Tab or a press) move through the cards and the peek shows whichever card has focus. That card is marked with the sidebar's open-row fill, and in the list the row is current.
  • Moving reads nothing. The peek shows the issue as the list holds it. The list re-reads every minute and after every write, so holding an arrow key sends no ISSUES_READ. There is no debounce. Changes made from the peek go through the list's own change, so the card moves the moment the answer lands. An issue that leaves the list (for example, archived) closes the peek.
  • One copy of the issue's content. issue-detail.tsx is split into IssueDocument (title, byline, Status/Priority chips, description) and IssueMenu (⋯: Copy link, Archive and its confirm). The page draws DetailPage with crumbs plus those two pieces. The peek draws a head (identifier, Open as page, ⋯, Close) over the same IssueDocument. #3648 (merged first) is folded in: its Tasks row is part of IssueDocument and its Start task… is part of IssueMenu, so both the page and the peek show them.
  • @luke/ui ListRow: onPress now receives the click event, so a row can read ⌘. Existing callers are unaffected, since it was already wired to onClick.

Not in this PR: Space to peek and ⌥⌘B, a peek floating over the board in windows too narrow to dock it, and peeks in history or links.

Screenshots

Rendered from the real components in a Playwright harness (IssuesPage over a stubbed bridge, 1280×760, sidebar folded).

Before: pressing ACME-1 replaced the board with the issue's page.

Before: the issue's page in the board's place

After: the same press opens the peek, the board stays in place, and ACME-1's card is marked.

After: the peek beside the board

Mid-peek, about halfway through the slide-in: the board took its new width at once, the pane slides in on transform, and the toolbar's buttons glide.

Mid-peek: the pane sliding in

After, light theme

Tests

New in issues-page.test.ts. Each fails without the change except the narrow-window one, which pins the fallback:

  • A press on a card opens a peek: the board stays, the card is marked, no page opens, nothing is read.
  • While a peek is open, ↓ and → move it from card to card without a read. Escape closes it and leaves the card focused, and a second Escape leaves Issues.
  • Close puts the peek away and returns focus to its card.
  • ⌘-press, ⌘Return, and Open as page open the issue's page.
  • In a window with no room, a press opens the page (the fallback).
  • A status picked in the peek is sent through the list, the card moves column, and the peek stays on the issue.
  • Archive from the peek's ⋯ removes the card and closes the peek.
  • In the List layout, a press on a row opens a peek and the row is current.
  • A peek whose window narrows below that room closes, and its card gets the keyboard back (Devin's review).
  • Archive's question asked in a peek goes away as soon as the peek starts sliding out, and listing another workspace opens no peek from the last one (Devin's review).
  • The peek lists the tasks started from its issue and offers Start task… from its ⋯, as the issue's page does (#3648).
  • An issue that a View options filter (#3667) leaves off the page closes its peek (Devin's review).
  • The board's columns glide, rather than jump, as the peek opens and closes (Devin's review). The recording motion engine moved to #testing/pane-motion, now that two suites use it.

Rewritten to the new paths because they pinned "Return/press opens the page":

  • "↓ and Return open the issue reached…" is now "↓ and ⌘Return open the issue reached…"
  • "on the board ↓ and ↑ move … and Return opens the card reached" now uses ⌘Return.
  • "on a board wider than the window … on the way back from its issue" now uses ⌘Return.
  • "View options lays the page out as the list … an issue opened from either goes back to it" now uses a ⌘-press.
  • "a card let go over its own column sends nothing, and a press that does not move still opens it" now uses a ⌘-press that opens the page.
  • The openIssue helper (used by the issue-page tests) and the "C … a c typed into … an issue's title" test now open the page with a ⌘-press.

pnpm check:affected: typecheck passed. lint, knip and discarded-effect were rerun one at a time after the parallel run ran out of memory, and all passed. Desktop, ui and web vitest suites pass.

Made with Luke