From 1b77838d1c0e80729bf574c16436bdc7f5154d77 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Mon, 21 Sep 2026 00:05:13 -0700 Subject: [PATCH 1/4] Consolidate source control tooltips to eliminate redundant hover text (#21733) * refactor(source-control): consolidate tooltips to avoid duplicates - Remove native title attributes from buttons that also render Radix tooltips - Introduce PrimaryActionTooltip wrapper that decides whether to show a tooltip based on context - Hide pure repeats: enabled Stage All and Create PR have no tooltip since the label already states the action - Show tooltips only when they add information: disabled reasons, commit shortcut, and remote counts * Show Create PR intent tooltip to explain the prepare step The Create PR intent label doesn't convey that clicking it stages, commits, and pushes before opening the PR dialog. Keep the tooltip to surface this multi-step operation that users might not expect. --- .../CommitArea.generate.test.tsx | 10 ++-- .../right-sidebar/CommitArea.test.tsx | 31 +++++++++- ...rce-control-primary-action-tooltip.test.ts | 30 ++++++++++ .../source-control-primary-action-tooltip.tsx | 57 +++++++++++++++++++ .../commit/commit-action-menu.tsx | 54 +++++++----------- .../source-control/commit/commit-area.tsx | 6 -- .../commit/commit-message-composer.tsx | 11 ---- .../panel/header-icon-button.tsx | 1 - .../source-control/panel/header-toolbar.tsx | 44 +++++++------- 9 files changed, 161 insertions(+), 83 deletions(-) create mode 100644 src/renderer/src/components/right-sidebar/source-control-primary-action-tooltip.test.ts create mode 100644 src/renderer/src/components/right-sidebar/source-control-primary-action-tooltip.tsx diff --git a/src/renderer/src/components/right-sidebar/CommitArea.generate.test.tsx b/src/renderer/src/components/right-sidebar/CommitArea.generate.test.tsx index b1ea39d3bf4..78663a7601f 100644 --- a/src/renderer/src/components/right-sidebar/CommitArea.generate.test.tsx +++ b/src/renderer/src/components/right-sidebar/CommitArea.generate.test.tsx @@ -105,7 +105,8 @@ describe('CommitArea AI generation', () => { const button = buttonByLabel(markup, 'Generate commit message with AI') expect(hasDisabledAttribute(button)).toBe(false) - expect(button).toContain('title="ai commit msg"') + // Why: single Radix tooltip only — native title removed to avoid duplicate tooltips. + expect(button).not.toContain('title=') }) it('disables AI generation when the textarea already has user text', () => { @@ -116,7 +117,7 @@ describe('CommitArea AI generation', () => { const button = buttonByLabel(markup, 'Generate commit message with AI') expect(button).toContain('aria-disabled="true"') - expect(button).toContain('title="Clear the message to regenerate."') + expect(button).not.toContain('title=') }) it('keeps AI generation discoverable when the configured agent needs attention', () => { @@ -152,7 +153,7 @@ describe('CommitArea AI generation', () => { const button = buttonByLabel(markup, 'Generate commit message with AI') expect(hasDisabledAttribute(button)).toBe(false) - expect(button).toContain('title="Pick an agent in Settings -> Git -> Source Control AI."') + expect(button).not.toContain('title=') }) it('turns the generating icon into a stop affordance', () => { @@ -165,7 +166,7 @@ describe('CommitArea AI generation', () => { }) const button = buttonByLabel(markup, 'Stop generating commit message') - expect(button).toContain('title="Stop generating"') + expect(button).not.toContain('title=') expect(button).toContain('lucide-refresh-cw') expect(button).toContain('lucide-square') }) @@ -214,7 +215,6 @@ describe('CommitArea AI generation', () => { expect(markup).not.toContain('aria-label="Commit message"') expect(markup).not.toContain('aria-label="Generate commit message with AI"') - expect(markup).toContain('Nothing to commit') expect(markup).toContain('aria-label="More commit and remote actions"') }) }) diff --git a/src/renderer/src/components/right-sidebar/CommitArea.test.tsx b/src/renderer/src/components/right-sidebar/CommitArea.test.tsx index 712a2c277d0..7222358c66e 100644 --- a/src/renderer/src/components/right-sidebar/CommitArea.test.tsx +++ b/src/renderer/src/components/right-sidebar/CommitArea.test.tsx @@ -197,6 +197,35 @@ describe('CommitArea', () => { expect(markupWin).toContain('Enter') }) + it('renders no tooltip on enabled Stage All — the label already states the action', () => { + const props = baseProps() + const markup = renderCommitArea({ + ...props, + primaryAction: { + kind: 'stage', + disabled: false, + label: 'Stage All', + title: 'Stage all changes' + } + }) + expect(firstButton(markup)).not.toContain('title=') + expect(markup).not.toContain('Stage all changes') + }) + + it('renders the disabled reason in the primary button tooltip', () => { + const props = baseProps() + const markup = renderCommitArea({ + ...props, + primaryAction: { + kind: 'commit', + disabled: true, + label: 'Commit', + title: 'Enter a commit message to commit' + } + }) + expect(markup).toContain('Enter a commit message to commit') + }) + it('only handles Cmd+Enter when focus is within the Source Control sidebar', () => { setUserAgent('Macintosh') const onPrimaryAction = vi.fn() @@ -583,8 +612,8 @@ describe('CommitArea', () => { expect(stageAllButton).not.toContain('disabled=""') expect(stageAllButton).toContain('lucide-plus') expect(stageAllButton).toContain('rounded-r-none') + expect(stageAllButton).not.toContain('title=') expect(markup).toContain('aria-label="More commit and remote actions"') - expect(markup).toContain('Stage all changes') expect( (markup.match(//g) ?? []).some((button) => button.includes('Commit') diff --git a/src/renderer/src/components/right-sidebar/source-control-primary-action-tooltip.test.ts b/src/renderer/src/components/right-sidebar/source-control-primary-action-tooltip.test.ts new file mode 100644 index 00000000000..fa3812c5f7d --- /dev/null +++ b/src/renderer/src/components/right-sidebar/source-control-primary-action-tooltip.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest' +import { shouldShowPrimaryTooltip } from './source-control-primary-action-tooltip' +import type { PrimaryActionKind } from './source-control-primary-action' + +function action(kind: PrimaryActionKind, disabled: boolean) { + return { kind, disabled, label: kind, title: kind } +} + +describe('shouldShowPrimaryTooltip', () => { + it('shows the tooltip when disabled — the title carries the blocking reason', () => { + for (const kind of ['stage', 'create_pr', 'create_pr_intent', 'commit'] as const) { + expect(shouldShowPrimaryTooltip(action(kind, true))).toBe(true) + } + }) + + it('hides pure repeats on enabled Stage All and Create PR', () => { + expect(shouldShowPrimaryTooltip(action('stage', false))).toBe(false) + expect(shouldShowPrimaryTooltip(action('create_pr', false))).toBe(false) + }) + + it('keeps the Create PR intent tooltip — it explains the prepare step the label omits', () => { + expect(shouldShowPrimaryTooltip(action('create_pr_intent', false))).toBe(true) + }) + + it('shows informative tooltips for commit and remote counts', () => { + for (const kind of ['commit', 'push', 'pull', 'sync', 'publish'] as const) { + expect(shouldShowPrimaryTooltip(action(kind, false))).toBe(true) + } + }) +}) diff --git a/src/renderer/src/components/right-sidebar/source-control-primary-action-tooltip.tsx b/src/renderer/src/components/right-sidebar/source-control-primary-action-tooltip.tsx new file mode 100644 index 00000000000..9dda7e3d579 --- /dev/null +++ b/src/renderer/src/components/right-sidebar/source-control-primary-action-tooltip.tsx @@ -0,0 +1,57 @@ +import React from 'react' +import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' +import { getScreenSubmitModifierLabel } from '@/lib/screen-submit-shortcut' +import type { PrimaryAction } from './source-control-primary-action' + +// Why: text primaries whose title merely repeats the label (enabled Stage All +// and Create PR) get no tooltip — pure noise. Tooltips stay only when they +// add info: disabled reasons, remote counts, the Commit shortcut, and the +// Create PR intent, whose label hides that the click also stages/commits/pushes. +export function shouldShowPrimaryTooltip( + primaryAction: Pick +): boolean { + if (primaryAction.disabled) { + return true + } + return ( + primaryAction.kind === 'commit' || + primaryAction.kind === 'create_pr_intent' || + primaryAction.kind === 'push' || + primaryAction.kind === 'pull' || + primaryAction.kind === 'sync' || + primaryAction.kind === 'publish' + ) +} + +// Why: both the commit-area split button and the header Create PR button share +// this show/hide rule, so the wrapper lives here instead of duplicating the +// Tooltip-or-plain-button branch (and the Button markup) at each call site. +export function PrimaryActionTooltip({ + action, + side, + children +}: { + action: PrimaryAction + side: 'top' | 'bottom' + children: React.JSX.Element +}): React.JSX.Element { + if (!shouldShowPrimaryTooltip(action)) { + return children + } + return ( + + {children} + + {action.kind === 'commit' ? ( + + {action.title} + + + ) : ( + {action.title} + )} + + + ) +} diff --git a/src/renderer/src/components/right-sidebar/source-control/commit/commit-action-menu.tsx b/src/renderer/src/components/right-sidebar/source-control/commit/commit-action-menu.tsx index cb871a7340c..84e4f221c97 100644 --- a/src/renderer/src/components/right-sidebar/source-control/commit/commit-action-menu.tsx +++ b/src/renderer/src/components/right-sidebar/source-control/commit/commit-action-menu.tsx @@ -1,12 +1,11 @@ import React from 'react' import { ChevronDown, Loader2 } from 'lucide-react' -import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo' import { Button } from '@/components/ui/button' import { DropdownMenu, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { getScreenSubmitModifierLabel } from '@/lib/screen-submit-shortcut' import { cn } from '@/lib/utils' import type { PrimaryAction } from '../../source-control-primary-action' +import { PrimaryActionTooltip } from '../../source-control-primary-action-tooltip' export function CommitActionMenu({ showComposer, @@ -15,7 +14,6 @@ export function CommitActionMenu({ showSpinner, showChevronSpinner, moreCommitAndRemoteActionsLabel, - moreActionsLabel, dropdownMenuContent, onPrimaryAction }: { @@ -28,7 +26,6 @@ export function CommitActionMenu({ showSpinner: boolean showChevronSpinner: boolean moreCommitAndRemoteActionsLabel: string - moreActionsLabel: string dropdownMenuContent: React.ReactNode onPrimaryAction: () => void }): React.JSX.Element { @@ -36,35 +33,25 @@ export function CommitActionMenu({ // Why: action + chevron form one split button so the edit → commit → push loop stays in a single vertical band.
- {/* Why: match the Checks hosted-review buttons so action-button shape is consistent across Source Control and Checks. */} - - - - - - - - {primaryAction.title} - {primaryAction.kind === 'commit' ? ( - - ) : null} - - + + + + + @@ -80,7 +67,6 @@ export function CommitActionMenu({ primaryAction.disabled && 'opacity-50' )} aria-label={moreCommitAndRemoteActionsLabel} - title={moreActionsLabel} > {showChevronSpinner ? ( diff --git a/src/renderer/src/components/right-sidebar/source-control/commit/commit-area.tsx b/src/renderer/src/components/right-sidebar/source-control/commit/commit-area.tsx index 7fa97394071..e73181161b0 100644 --- a/src/renderer/src/components/right-sidebar/source-control/commit/commit-area.tsx +++ b/src/renderer/src/components/right-sidebar/source-control/commit/commit-area.tsx @@ -165,10 +165,6 @@ export function CommitArea({ 'auto.components.right.sidebar.SourceControl.cc199ccc5f', 'More commit and remote actions' ) - const moreActionsLabel = translate( - 'auto.components.right.sidebar.SourceControl.4d6e1fd7f3', - 'More actions' - ) const dropdownMenuContent = ( {dropdownItems.map((entry) => @@ -180,7 +176,6 @@ export function CommitArea({
{ @@ -235,7 +230,6 @@ export function CommitArea({ showSpinner={showSpinner} showChevronSpinner={showChevronSpinner} moreCommitAndRemoteActionsLabel={moreCommitAndRemoteActionsLabel} - moreActionsLabel={moreActionsLabel} dropdownMenuContent={dropdownMenuContent} onPrimaryAction={onPrimaryAction} /> diff --git a/src/renderer/src/components/right-sidebar/source-control/commit/commit-message-composer.tsx b/src/renderer/src/components/right-sidebar/source-control/commit/commit-message-composer.tsx index 6553aaaee2a..be1adcae9d5 100644 --- a/src/renderer/src/components/right-sidebar/source-control/commit/commit-message-composer.tsx +++ b/src/renderer/src/components/right-sidebar/source-control/commit/commit-message-composer.tsx @@ -56,10 +56,6 @@ export function CommitMessageComposer({ - - - - {action.title} - - + + + + + ) } From 8d42410e0168cfe0b61c382fd84ab081fc317d3e Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Mon, 21 Sep 2026 03:32:34 -0400 Subject: [PATCH 2/4] feat(mobile): render the HTML preview on the page in a sealed srcdoc frame (OTA phase C, C7.10 A) (#21862) * feat(mobile): offer a cancelled top-frame navigation to the shell's opener Both shells cancelled every navigation off their own document in silence: iOS `decidePolicyFor` allowed only `isMainFrame && isDocumentUrl`, Android's `shouldOverrideUrlLoading` dropped anything whose resolved path was not "/". Nothing opened. That is the whole of ruling 29's "if they do not": a user tapping a link inside C7.10's sealed HTML-preview frame reaches the top frame as a navigation request, and the shell was the only thing that could act on it. A cancelled main-frame navigation now reaches JS as `onExternalNavigation` and goes through the same `Linking.openURL` the `externalLink` notify already uses. The scheme list is not restated natively: the native side caps the string and says which frame it came from, and `readBridgeExternalLinkUrl` decides what opens in the half that ships over the air. A subframe navigation is never offered, because that is the sealed preview loading itself. swiftc check: OK (`checkCancelledNavigation` added, the whole suite runs). Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): render the HTML preview in a sealed srcdoc frame on the page C7.6 gave the page the artifact's source, which is the native component's Source tab and half its job (ruling 8). Ruling 26 makes that debt: the Preview tab comes back as an `