| ... | @@ -171,7 +171,7 @@ const LAUNCHER_SHORTCUT_BLOCKING_LAYERS = [ | ... | @@ -171,7 +171,7 @@ const LAUNCHER_SHORTCUT_BLOCKING_LAYERS = [ |
| 171 | '[data-slot="autocomplete-popup"]', | 171 | '[data-slot="autocomplete-popup"]', |
| 172 | ].join(","); | 172 | ].join(","); |
| 173 | | 173 | |
| 174 | /** One-line unavailability hints for the empty-state cards. */ | 174 | /** One-line unavailability hints for the empty-state rows. */ |
| 175 | const SURFACE_UNAVAILABLE_HINTS = { | 175 | const SURFACE_UNAVAILABLE_HINTS = { |
| 176 | browser: "Only available in the desktop app.", | 176 | browser: "Only available in the desktop app.", |
| 177 | terminal: "Available when a project is open.", | 177 | terminal: "Available when a project is open.", |
| ... | @@ -304,7 +304,7 @@ function SurfaceMenuItem(props: { | ... | @@ -304,7 +304,7 @@ function SurfaceMenuItem(props: { |
| 304 | } | 304 | } |
| 305 | | 305 | |
| 306 | /** | 306 | /** |
| 307 | * Card launcher shown when the right panel has no surfaces. Keyboard-first | 307 | * List launcher shown when the right panel has no surfaces. Keyboard-first |
| 308 | * without palette chrome: a surface's letter opens it directly from anywhere | 308 | * without palette chrome: a surface's letter opens it directly from anywhere |
| 309 | * outside a typing context, and arrows plus Enter work while the launcher is | 309 | * outside a typing context, and arrows plus Enter work while the launcher is |
| 310 | * focused. The highlight only appears on hover or arrow use. Unavailable | 310 | * focused. The highlight only appears on hover or arrow use. Unavailable |
| ... | @@ -337,7 +337,6 @@ function RightPanelEmptyState(props: { | ... | @@ -337,7 +337,6 @@ function RightPanelEmptyState(props: { |
| 337 | const actions = [ | 337 | const actions = [ |
| 338 | { | 338 | { |
| 339 | label: "Browser", | 339 | label: "Browser", |
| 340 | description: "Open a local app or URL.", | | |
| 341 | icon: Globe2, | 340 | icon: Globe2, |
| 342 | shortcut: "B", | 341 | shortcut: "B", |
| 343 | available: props.browserAvailable, | 342 | available: props.browserAvailable, |
| ... | @@ -347,7 +346,6 @@ function RightPanelEmptyState(props: { | ... | @@ -347,7 +346,6 @@ function RightPanelEmptyState(props: { |
| 347 | }, | 346 | }, |
| 348 | { | 347 | { |
| 349 | label: "Terminal", | 348 | label: "Terminal", |
| 350 | description: "Start a shell in this workspace.", | | |
| 351 | icon: TerminalSquare, | 349 | icon: TerminalSquare, |
| 352 | shortcut: "T", | 350 | shortcut: "T", |
| 353 | available: props.terminalAvailable, | 351 | available: props.terminalAvailable, |
| ... | @@ -357,7 +355,6 @@ function RightPanelEmptyState(props: { | ... | @@ -357,7 +355,6 @@ function RightPanelEmptyState(props: { |
| 357 | }, | 355 | }, |
| 358 | { | 356 | { |
| 359 | label: "Files", | 357 | label: "Files", |
| 360 | description: "Browse and read workspace files.", | | |
| 361 | icon: Files, | 358 | icon: Files, |
| 362 | shortcut: "F", | 359 | shortcut: "F", |
| 363 | available: props.filesAvailable, | 360 | available: props.filesAvailable, |
| ... | @@ -367,7 +364,6 @@ function RightPanelEmptyState(props: { | ... | @@ -367,7 +364,6 @@ function RightPanelEmptyState(props: { |
| 367 | }, | 364 | }, |
| 368 | { | 365 | { |
| 369 | label: "Diff", | 366 | label: "Diff", |
| 370 | description: "Review changes in this thread.", | | |
| 371 | icon: FileDiff, | 367 | icon: FileDiff, |
| 372 | shortcut: "D", | 368 | shortcut: "D", |
| 373 | available: props.diffAvailable, | 369 | available: props.diffAvailable, |
| ... | @@ -377,7 +373,6 @@ function RightPanelEmptyState(props: { | ... | @@ -377,7 +373,6 @@ function RightPanelEmptyState(props: { |
| 377 | }, | 373 | }, |
| 378 | { | 374 | { |
| 379 | label: "Pull request", | 375 | label: "Pull request", |
| 380 | description: "Open this branch's pull request.", | | |
| 381 | icon: GitPullRequest, | 376 | icon: GitPullRequest, |
| 382 | shortcut: "P", | 377 | shortcut: "P", |
| 383 | available: props.pullRequestAvailable, | 378 | available: props.pullRequestAvailable, |
| ... | @@ -387,7 +382,6 @@ function RightPanelEmptyState(props: { | ... | @@ -387,7 +382,6 @@ function RightPanelEmptyState(props: { |
| 387 | }, | 382 | }, |
| 388 | { | 383 | { |
| 389 | label: "Linked pull requests", | 384 | label: "Linked pull requests", |
| 390 | description: "Every pull request this thread has linked, stacks included.", | | |
| 391 | icon: GitPullRequestArrow, | 385 | icon: GitPullRequestArrow, |
| 392 | shortcut: "L", | 386 | shortcut: "L", |
| 393 | available: props.pullRequestsAvailable, | 387 | available: props.pullRequestsAvailable, |
| ... | @@ -397,7 +391,6 @@ function RightPanelEmptyState(props: { | ... | @@ -397,7 +391,6 @@ function RightPanelEmptyState(props: { |
| 397 | }, | 391 | }, |
| 398 | { | 392 | { |
| 399 | label: "Agents", | 393 | label: "Agents", |
| 400 | description: "Follow subagents and workflows.", | | |
| 401 | icon: Bot, | 394 | icon: Bot, |
| 402 | shortcut: "A", | 395 | shortcut: "A", |
| 403 | available: props.agentsAvailable, | 396 | available: props.agentsAvailable, |
| ... | @@ -464,9 +457,8 @@ function RightPanelEmptyState(props: { | ... | @@ -464,9 +457,8 @@ function RightPanelEmptyState(props: { |
| 464 | return; | 457 | return; |
| 465 | } | 458 | } |
| 466 | if (event.key === "Enter") { | 459 | if (event.key === "Enter") { |
| 467 | // A focused card button owns its own activation; only open from the | 460 | // Only activate the highlight when the launcher itself has focus. |
| 468 | // highlight when the container itself has focus. | 461 | if (event.target !== event.currentTarget) return; |
| 469 | if (event.target instanceof HTMLElement && event.target.closest("button")) return; | | |
| 470 | const action = availableActions[highlightIndex]; | 462 | const action = availableActions[highlightIndex]; |
| 471 | if (!action) return; | 463 | if (!action) return; |
| 472 | event.preventDefault(); | 464 | event.preventDefault(); |
| ... | @@ -500,10 +492,6 @@ function RightPanelEmptyState(props: { | ... | @@ -500,10 +492,6 @@ function RightPanelEmptyState(props: { |
| 500 | ); | 492 | ); |
| 501 | }; | 493 | }; |
| 502 | | 494 | |
| 503 | const cardShellClass = | | |
| 504 | "rounded-lg border border-border/80 bg-card dark:border-transparent dark:shadow-none dark:inset-ring-1 dark:inset-ring-white/5"; | | |
| 505 | const highlightedCardClass = "bg-accent/60 dark:inset-ring-white/20"; | | |
| 506 | | | |
| 507 | return ( | 495 | return ( |
| 508 | <div | 496 | <div |
| 509 | ref={focusOnMount} | 497 | ref={focusOnMount} |
| ... | @@ -514,24 +502,19 @@ function RightPanelEmptyState(props: { | ... | @@ -514,24 +502,19 @@ function RightPanelEmptyState(props: { |
| 514 | className={cn( | 502 | className={cn( |
| 515 | "flex min-h-0 flex-1 items-center justify-center overflow-y-auto px-6 pt-6 outline-none", | 503 | "flex min-h-0 flex-1 items-center justify-center overflow-y-auto px-6 pt-6 outline-none", |
| 516 | // The panel topbar sits above this container; matching bottom padding | 504 | // The panel topbar sits above this container; matching bottom padding |
| 517 | // keeps the cards centered against the full panel, not the leftover. | 505 | // keeps the list centered against the full panel, not the leftover. |
| 518 | "pb-[calc(var(--workspace-topbar-height)+--spacing(6))]", | 506 | "pb-[calc(var(--workspace-topbar-height)+--spacing(6))]", |
| 519 | )} | 507 | )} |
| 520 | > | 508 | > |
| 521 | <div className="relative w-full max-w-lg"> | 509 | <div className="w-full max-w-xs"> |
| 522 | <div className="absolute inset-x-0 bottom-full mb-5 text-center"> | 510 | <h3 className="mb-3 text-center font-medium text-foreground text-sm">Open a surface</h3> |
| 523 | <h3 className="font-medium text-foreground text-sm">Open a surface</h3> | 511 | <div className="flex flex-col gap-0.5"> |
| 524 | <p className="mt-1 text-muted-foreground text-xs"> | | |
| 525 | Choose what to show in the right panel. | | |
| 526 | </p> | | |
| 527 | </div> | | |
| 528 | <div className="grid grid-cols-2 gap-2"> | | |
| 529 | {actions.map((action) => | 512 | {actions.map((action) => |
| 530 | action.available ? ( | 513 | action.available ? ( |
| 531 | // The card is itself a button, so the profile chooser sits beside | 514 | // The row is itself a button, so the profile chooser sits beside |
| 532 | // it in a wrapper rather than inside it. Hover lives on the | 515 | // it in a wrapper rather than inside it. Hover lives on the |
| 533 | // wrapper: the chooser overlays the card, and a pointer moving | 516 | // wrapper: the chooser overlays the row, and a pointer moving |
| 534 | // onto it must not read as leaving the card. | 517 | // onto it must not read as leaving the row. |
| 535 | <div | 518 | <div |
| 536 | key={action.label} | 519 | key={action.label} |
| 537 | className="group relative" | 520 | className="group relative" |
| ... | @@ -546,25 +529,23 @@ function RightPanelEmptyState(props: { | ... | @@ -546,25 +529,23 @@ function RightPanelEmptyState(props: { |
| 546 | type="button" | 529 | type="button" |
| 547 | onClick={action.onClick} | 530 | onClick={action.onClick} |
| 548 | className={cn( | 531 | className={cn( |
| 549 | // Full height: the wrapper is the grid item that stretches | 532 | "flex h-8 w-full cursor-pointer items-center gap-2.5 rounded-[var(--control-radius)] px-2.5 text-left text-sm transition-colors group-hover:bg-accent/60", |
| 550 | // to the row, so the button must fill it to stay level with | 533 | isHighlighted(action) && "bg-accent/60", |
| 551 | // its neighbour and keep the chooser anchored inside. | | |
| 552 | "relative flex h-full w-full cursor-pointer flex-col items-start p-4 text-left transition group-hover:border-border group-hover:bg-accent/60", | | |
| 553 | cardShellClass, | | |
| 554 | isHighlighted(action) && highlightedCardClass, | | |
| 555 | )} | 534 | )} |
| 556 | > | 535 | > |
| 557 | <Kbd className="absolute top-3 right-3">{action.shortcut}</Kbd> | 536 | {actionIcon(action, "size-4")} |
| 558 | <span className="flex items-center gap-2 pe-8"> | 537 | <span |
| 559 | {actionIcon(action)} | 538 | className={cn( |
| 560 | <span className="font-medium text-sm">{action.label}</span> | 539 | "min-w-0 flex-1 truncate", |
| 561 | </span> | 540 | action.label === "Browser" && props.browserProfiles.length > 1 && "pr-7", |
| 562 | <span className="mt-1.5 text-muted-foreground text-xs leading-relaxed"> | 541 | )} |
| 563 | {action.description} | 542 | > |
| | 543 | {action.label} |
| 564 | </span> | 544 | </span> |
| | 545 | <Kbd>{action.shortcut}</Kbd> |
| 565 | </button> | 546 | </button> |
| 566 | {/* | 547 | {/* |
| 567 | Same choice the tab bar's "+" menu offers: the card opens the | 548 | Same choice the tab bar's "+" menu offers: the row opens the |
| 568 | default profile, the chevron picks another. Only worth showing | 549 | default profile, the chevron picks another. Only worth showing |
| 569 | once there is something to choose between. | 550 | once there is something to choose between. |
| 570 | */} | 551 | */} |
| ... | @@ -574,7 +555,7 @@ function RightPanelEmptyState(props: { | ... | @@ -574,7 +555,7 @@ function RightPanelEmptyState(props: { |
| 574 | render={ | 555 | render={ |
| 575 | <Button | 556 | <Button |
| 576 | aria-label="Open browser in a profile" | 557 | aria-label="Open browser in a profile" |
| 577 | className="absolute right-3 bottom-3 [--control-icon-color:currentColor]" | 558 | className="absolute top-1/2 right-8 -translate-y-1/2 [--control-icon-color:currentColor]" |
| 578 | size="icon-xs" | 559 | size="icon-xs" |
| 579 | variant="ghost-muted" | 560 | variant="ghost-muted" |
| 580 | /> | 561 | /> |
| ... | @@ -601,22 +582,21 @@ function RightPanelEmptyState(props: { | ... | @@ -601,22 +582,21 @@ function RightPanelEmptyState(props: { |
| 601 | ) : null} | 582 | ) : null} |
| 602 | </div> | 583 | </div> |
| 603 | ) : ( | 584 | ) : ( |
| 604 | <div | 585 | <DisabledReasonTooltip |
| 605 | key={action.label} | 586 | key={action.label} |
| 606 | className={cn( | 587 | reason={action.disabledReason} |
| 607 | "relative flex w-full flex-col items-start p-4 opacity-40", | 588 | trigger={ |
| 608 | cardShellClass, | 589 | <div |
| 609 | )} | 590 | tabIndex={0} |
| 610 | > | 591 | aria-disabled="true" |
| 611 | <Kbd className="absolute top-3 right-3">{action.shortcut}</Kbd> | 592 | className="flex h-8 w-full cursor-default items-center gap-2.5 rounded-[var(--control-radius)] px-2.5 text-left text-sm opacity-50" |
| 612 | <span className="flex items-center gap-2 pe-8"> | 593 | > |
| 613 | {actionIcon(action)} | 594 | {actionIcon(action, "size-4")} |
| 614 | <span className="font-medium text-sm">{action.label}</span> | 595 | <span className="min-w-0 flex-1 truncate">{action.label}</span> |
| 615 | </span> | 596 | <Kbd>{action.shortcut}</Kbd> |
| 616 | <span className="mt-1.5 text-muted-foreground text-xs leading-relaxed"> | 597 | </div> |
| 617 | {action.disabledReason} | 598 | } |
| 618 | </span> | 599 | /> |
| 619 | </div> | | |
| 620 | ), | 600 | ), |
| 621 | )} | 601 | )} |
| 622 | </div> | 602 | </div> |