diff --git a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx index 9eb7cef2e..9f54c3c84 100644 --- a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx +++ b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx @@ -145,6 +145,7 @@ import { localizedHostedReviewCopy } from '@/i18n/hosted-review-localized-copy' import { translate } from '@/i18n/i18n' import { groupPRComments, type PRCommentGroup } from '@/lib/pr-comment-groups' import { openChecksPanelHostedReviewUrl } from './checks-panel-hosted-review-click-routing' +import { ChecksPanelUpdatedAtMetadata } from './checks-panel-updated-at-metadata' import { clearPullRequestGenerationRequiresPushBeforeCreate, createRunningPullRequestGenerationRecord, @@ -3469,11 +3470,10 @@ export default function ChecksPanel(): React.JSX.Element { {/* Updated at */} {activeReview.updatedAt && ( -
- {reviewShortLabel}{' '} - {translate('auto.components.right.sidebar.ChecksPanel.34464d00b9', 'updated')} - {new Date(activeReview.updatedAt).toLocaleString()} -
+ )} {/* Merge / Delete Workspace actions */} {activeReview && activeWorktree && repo && ( diff --git a/src/renderer/src/components/right-sidebar/ChecksPanel.updated-at-metadata.test.tsx b/src/renderer/src/components/right-sidebar/ChecksPanel.updated-at-metadata.test.tsx new file mode 100644 index 000000000..48e9dc210 --- /dev/null +++ b/src/renderer/src/components/right-sidebar/ChecksPanel.updated-at-metadata.test.tsx @@ -0,0 +1,28 @@ +// @vitest-environment happy-dom + +import { renderToStaticMarkup } from 'react-dom/server' +import { describe, expect, it } from 'vitest' +import { ChecksPanelUpdatedAtMetadata } from './checks-panel-updated-at-metadata' + +function renderMetadataText(reviewShortLabel: string): string { + const container = document.createElement('div') + container.innerHTML = renderToStaticMarkup( + + ) + return container.textContent ?? '' +} + +describe('ChecksPanel updated-at metadata', () => { + it.each(['PR', 'MR'])( + 'renders a whitespace boundary between the %s updated label and timestamp', + (reviewShortLabel) => { + const text = renderMetadataText(reviewShortLabel) + + expect(text).toMatch(new RegExp(`${reviewShortLabel} updated\\s+\\S`)) + expect(text).not.toMatch(new RegExp(`${reviewShortLabel} updated\\S`)) + } + ) +}) diff --git a/src/renderer/src/components/right-sidebar/checks-panel-content.test.tsx b/src/renderer/src/components/right-sidebar/checks-panel-content.test.tsx index 5ad62f79f..c3fa3ffd2 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel-content.test.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel-content.test.tsx @@ -273,6 +273,9 @@ describe('PRCommentsList', () => { expect(markup.indexOf('aria-label="Add comment"')).toBeLessThan( markup.indexOf('Existing review context') ) + expect(markup.indexOf('aria-label="Comment display options"')).toBeLessThan( + markup.indexOf('aria-label="Add comment"') + ) expect(markup).toContain('lucide-plus') expect(markup).not.toContain('Add a comment...') expect(markup).not.toContain('Add a PR comment') diff --git a/src/renderer/src/components/right-sidebar/checks-panel-content.tsx b/src/renderer/src/components/right-sidebar/checks-panel-content.tsx index b3dc98cc7..988856a82 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel-content.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel-content.tsx @@ -2461,6 +2461,42 @@ export function PRCommentsList({ )} )} + {comments.length > 0 && ( + + + + + + + {translate( + 'auto.components.right.sidebar.checks.panel.content.5e6e5a13fa', + 'View' + )} + + setDisplayMode(value as PRCommentsListDisplayMode)} + > + {PR_COMMENT_LIST_DISPLAY_MODES.map((mode) => ( + + {getPRCommentsListDisplayModeLabel(mode)} + + ))} + + + + )} {onAddComment && !isAddingComment && ( @@ -2502,42 +2538,6 @@ export function PRCommentsList({ )} - {comments.length > 0 && ( - - - - - - - {translate( - 'auto.components.right.sidebar.checks.panel.content.5e6e5a13fa', - 'View' - )} - - setDisplayMode(value as PRCommentsListDisplayMode)} - > - {PR_COMMENT_LIST_DISPLAY_MODES.map((mode) => ( - - {getPRCommentsListDisplayModeLabel(mode)} - - ))} - - - - )} {comments.length > 0 && ( diff --git a/src/renderer/src/components/right-sidebar/checks-panel-updated-at-metadata.tsx b/src/renderer/src/components/right-sidebar/checks-panel-updated-at-metadata.tsx new file mode 100644 index 000000000..4c4a90b7e --- /dev/null +++ b/src/renderer/src/components/right-sidebar/checks-panel-updated-at-metadata.tsx @@ -0,0 +1,20 @@ +import type React from 'react' +import { translate } from '@/i18n/i18n' + +type ChecksPanelUpdatedAtMetadataProps = { + reviewShortLabel: string + updatedAt: string +} + +export function ChecksPanelUpdatedAtMetadata({ + reviewShortLabel, + updatedAt +}: ChecksPanelUpdatedAtMetadataProps): React.JSX.Element { + return ( +
+ {reviewShortLabel}{' '} + {translate('auto.components.right.sidebar.ChecksPanel.34464d00b9', 'updated')}{' '} + {new Date(updatedAt).toLocaleString()} +
+ ) +} diff --git a/src/renderer/src/components/right-sidebar/pr-comment-presentation.test.ts b/src/renderer/src/components/right-sidebar/pr-comment-presentation.test.ts index cfee6f48d..b85c7085d 100644 --- a/src/renderer/src/components/right-sidebar/pr-comment-presentation.test.ts +++ b/src/renderer/src/components/right-sidebar/pr-comment-presentation.test.ts @@ -15,15 +15,28 @@ describe('pr-comment-presentation', () => { it('returns card layout tokens for cards and focus variants', () => { const cards = getPRCommentPresentationClasses('cards') expect(cards.useCardLayout).toBe(true) - expect(cards.commentBody).toContain('text-[13px]') + expect(cards.commentBody).toContain('text-xs') + expect(cards.commentBody).toContain('leading-5') expect(cards.commentBody).toContain('text-foreground') expect(cards.group).toContain('bg-secondary') expect(cards.group).toContain('shadow-xs') expect(cards.avatar).toContain('border-border') expect(cards.avatar).toContain('bg-background') - expect(getPRCommentPresentationClasses('focus').useCardLayout).toBe(true) - expect(getPRCommentPresentationClasses('focus').commentBody).toContain('text-[14px]') + const focus = getPRCommentPresentationClasses('focus') + expect(focus.useCardLayout).toBe(true) + expect(focus.commentBody).toContain('text-xs') + expect(focus.commentBody).toContain('leading-5') + expect(focus.commentBodyReply).toContain('text-xs') + expect(focus.commentBodyReply).toContain('leading-5') + expect(focus.author).toContain('text-[13px]') + expect(focus.list).toContain('gap-2') + expect(focus.commentBody).toContain('px-4 py-2.5') + expect(focus.commentBodyReply).toContain('px-4 py-2.5') + expect(focus.commentHeader).toContain('px-3 py-2') + expect(focus.commentHeaderReply).toContain('px-3 py-2') + expect(focus.commentHeaderMeta).toContain('pl-7') + expect(focus.commentHeaderMetaWithSelection).toContain('pl-[3.25rem]') }) it('preserves the legacy flat layout tokens', () => { diff --git a/src/renderer/src/components/right-sidebar/pr-comment-presentation.ts b/src/renderer/src/components/right-sidebar/pr-comment-presentation.ts index 7b9a645b9..7d6ca93f2 100644 --- a/src/renderer/src/components/right-sidebar/pr-comment-presentation.ts +++ b/src/renderer/src/components/right-sidebar/pr-comment-presentation.ts @@ -96,6 +96,15 @@ const COMMENT_AVATAR = const RESOLVED_SECTION_LABEL = 'text-[11px] font-semibold uppercase tracking-wider text-muted-foreground' +// Why: markdown bodies need the secondary sidebar scale; 13px relaxed copy reads oversized in narrow comment cards. +const CARD_COMMENT_BODY_SIZE = 'text-xs leading-5' +const CARD_COMMENT_AUTHOR_SIZE = 'text-[13px]' +const CARD_COMMENT_LIST_GAP = 'gap-2' +const CARD_COMMENT_BODY_PADDING = 'px-4 py-2.5' +const CARD_COMMENT_HEADER_PADDING = 'px-3 py-2' +const CARD_COMMENT_META_INDENT = 'pl-7' +const CARD_COMMENT_META_SELECTION_INDENT = 'pl-[3.25rem]' + const RESOLVED_SECTION_TRIGGER = cn( RESOLVED_SECTION_LABEL, 'rounded-none border-0 bg-transparent px-3 py-2 shadow-none hover:bg-accent/40 hover:text-foreground hover:no-underline' @@ -172,29 +181,21 @@ export function getPRCommentPresentationClasses( } } - const isFocus = variant === 'focus' - const bodySize = isFocus ? 'text-[14px] leading-relaxed' : 'text-[13px] leading-relaxed' - const authorSize = isFocus ? 'text-[14px]' : 'text-[13px]' - const listGap = isFocus ? 'gap-3' : 'gap-2' - const bodyPadding = isFocus ? 'px-4 py-3' : 'px-4 py-2.5' - const headerPadding = isFocus ? 'px-3.5 py-2.5' : 'px-3 py-2' - const metaIndent = isFocus ? 'pl-8' : 'pl-7' - return { variant, useCardLayout: true, - list: `flex flex-col ${listGap} px-3 py-2`, + list: `flex flex-col ${CARD_COMMENT_LIST_GAP} px-3 py-2`, group: COMMENT_CARD_SURFACE, groupStandalone: '', groupThread: '', commentRow: 'group/comment', commentRowReply: `border-t ${COMMENT_CARD_DIVIDER} bg-muted/25 dark:bg-muted/10`, - commentHeader: `flex flex-col gap-1 border-b ${COMMENT_CARD_DIVIDER} ${headerPadding}`, - commentHeaderReply: `flex min-w-0 items-center gap-2 ${headerPadding}`, - commentBody: `${bodyPadding} ${bodySize} text-foreground`, - commentBodyReply: `${bodyPadding} ${bodySize} text-foreground`, + commentHeader: `flex flex-col gap-1 border-b ${COMMENT_CARD_DIVIDER} ${CARD_COMMENT_HEADER_PADDING}`, + commentHeaderReply: `flex min-w-0 items-center gap-2 ${CARD_COMMENT_HEADER_PADDING}`, + commentBody: `${CARD_COMMENT_BODY_PADDING} ${CARD_COMMENT_BODY_SIZE} text-foreground`, + commentBodyReply: `${CARD_COMMENT_BODY_PADDING} ${CARD_COMMENT_BODY_SIZE} text-foreground`, commentBodyMarkdown: MARKDOWN_BASE, - author: `min-w-0 flex-1 truncate ${authorSize} font-semibold text-foreground`, + author: `min-w-0 flex-1 truncate ${CARD_COMMENT_AUTHOR_SIZE} font-semibold text-foreground`, authorResolved: 'text-muted-foreground', avatar: `size-5 ${COMMENT_AVATAR}`, avatarReply: `size-4 ${COMMENT_AVATAR}`, @@ -224,12 +225,12 @@ export function getPRCommentPresentationClasses( 'shrink-0 rounded border border-ring/40 bg-accent px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-foreground', commentHeaderPrimary: 'flex min-w-0 items-center gap-2', commentHeaderMeta: cn( - metaIndent, + CARD_COMMENT_META_INDENT, 'flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-muted-foreground' ), // Why: checkbox (16px) + gap-2 sits before the avatar row the meta row already indents past. commentHeaderMetaWithSelection: cn( - isFocus ? 'pl-[3.5rem]' : 'pl-[3.25rem]', + CARD_COMMENT_META_SELECTION_INDENT, 'flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-muted-foreground' ), // Why: open state is conveyed by the status badge; a green card rail reads noisy in the sidebar.