From b28ad03d5872b09355aecfa417ba72db0c4485db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:44:27 +0000 Subject: [PATCH 01/19] Initial plan From eadb46736ffded882e123c065ce8bb5528a7ce3d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:48:13 +0000 Subject: [PATCH 02/19] Initial plan for sticky PR/Issue headers Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/@types/vscode.proposed.chatParticipantAdditions.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 71520fa1ec..aa7001a3d2 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -105,6 +105,7 @@ declare module 'vscode' { isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; fromSubAgent?: boolean; + presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); } From f3da8333ad74505240e0a1e26d8af864b1412a71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:52:22 +0000 Subject: [PATCH 03/19] Add sticky header functionality with compact mode Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/components/header.tsx | 29 +++++++++++------ webviews/editorWebview/index.css | 49 +++++++++++++++++++++++++++++ webviews/editorWebview/overview.tsx | 16 ++++++++-- 3 files changed, 83 insertions(+), 11 deletions(-) diff --git a/webviews/components/header.tsx b/webviews/components/header.tsx index 9418515e19..f20d14674b 100644 --- a/webviews/components/header.tsx +++ b/webviews/components/header.tsx @@ -32,8 +32,9 @@ export function Header({ owner, repo, busy, - stateReason -}: PullRequest) { + stateReason, + isCompact +}: PullRequest & { isCompact?: boolean }) { const [currentTitle, setCurrentTitle] = useStateProp(title); const [inEditMode, setEditMode] = useState(false); const codingAgentEvent = mostRecentCopilotEvent(events); @@ -51,9 +52,16 @@ export function Header({ canEdit={canEdit} owner={owner} repo={repo} + isCompact={isCompact} /> - + {!isCompact && }
+ {isCompact && ( +
+ {getStatus(state, !!isDraft, isIssue, stateReason).icon} + {getStatus(state, !!isDraft, isIssue, stateReason).text} +
+ )} +

{' '} @@ -128,14 +137,16 @@ function Title({ title, titleHTML, number, url, inEditMode, setEditMode, setCurr #{number}

- {canEdit ? + {!isCompact && canEdit ? : null} - + {!isCompact && ( + + )}
); diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css index 4716e0152e..c34e8d5727 100644 --- a/webviews/editorWebview/index.css +++ b/webviews/editorWebview/index.css @@ -53,6 +53,45 @@ textarea:focus, margin: 20px 0 24px; padding-bottom: 24px; border-bottom: 1px solid var(--vscode-list-inactiveSelectionBackground); + transition: all 0.2s ease; +} + +.title.sticky { + position: sticky; + top: 0; + z-index: 100; + background: var(--vscode-editor-background); + margin: 0; + padding: 8px 0; + border-bottom: 1px solid var(--vscode-panel-border); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); +} + +.title.sticky .details { + width: 100%; +} + +.title.sticky .overview-title h2 { + font-size: 18px; + margin: 0; +} + +.title.sticky .header-actions { + padding-top: 0; +} + +.overview-title.compact { + display: flex; + align-items: center; + gap: 8px; +} + +.overview-title.compact h2 { + font-size: 18px; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .title .pr-number { @@ -563,6 +602,16 @@ small-button { display: flex; gap: 8px; padding-top: 4px; + align-items: center; +} + +.title.sticky .header-actions { + flex-wrap: nowrap; +} + +.title.sticky .header-actions #status { + margin-right: 0; + flex-shrink: 0; } .header-actions>div:first-of-type { diff --git a/webviews/editorWebview/overview.tsx b/webviews/editorWebview/overview.tsx index 36aeea2834..2974e1620b 100644 --- a/webviews/editorWebview/overview.tsx +++ b/webviews/editorWebview/overview.tsx @@ -31,11 +31,23 @@ const useMediaQuery = (query: string) => { export const Overview = (pr: PullRequest) => { const isSingleColumnLayout = useMediaQuery('(max-width: 768px)'); + const [isSticky, setIsSticky] = React.useState(false); + + React.useEffect(() => { + const handleScroll = () => { + // Make header sticky when scrolled past 80px + const scrolled = window.scrollY > 80; + setIsSticky(scrolled); + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); return <> -
+
-
+
{isSingleColumnLayout ? From 79ab7d6be112ab3932f144a8683b966a6da525c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:55:12 +0000 Subject: [PATCH 04/19] Add high contrast mode and mobile responsiveness support Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/editorWebview/index.css | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css index c34e8d5727..48e6c6be70 100644 --- a/webviews/editorWebview/index.css +++ b/webviews/editorWebview/index.css @@ -1261,6 +1261,11 @@ code { border-bottom: 1px solid var(--vscode-contrastBorder); } +.vscode-high-contrast .title.sticky { + border: 1px solid var(--vscode-contrastBorder); + box-shadow: none; +} + .vscode-high-contrast .diff .diffLine { background: none; } @@ -1287,6 +1292,19 @@ code { padding-bottom: 0px; } + .title.sticky { + padding: 8px 0; + border-bottom: 1px solid var(--vscode-panel-border); + } + + .title.sticky .overview-title h2 { + font-size: 16px; + } + + .title.sticky .button-group { + flex-wrap: nowrap; + } + #app { display: block; } From e52df1bbdb49979c4983360862d4a3d4ff73fbbc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:57:41 +0000 Subject: [PATCH 05/19] Add test for sticky header functionality Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/editorWebview/test/overview.test.tsx | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 webviews/editorWebview/test/overview.test.tsx diff --git a/webviews/editorWebview/test/overview.test.tsx b/webviews/editorWebview/test/overview.test.tsx new file mode 100644 index 0000000000..9a6e5980a3 --- /dev/null +++ b/webviews/editorWebview/test/overview.test.tsx @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { default as assert } from 'assert'; +import * as React from 'react'; +import { cleanup, render } from 'react-testing-library'; +import { createSandbox, SinonSandbox } from 'sinon'; + +import { PRContext, default as PullRequestContext } from '../../common/context'; +import { Overview } from '../overview'; +import { PullRequestBuilder } from './builder/pullRequest'; + +describe('Overview', function () { + let sinon: SinonSandbox; + + beforeEach(function () { + sinon = createSandbox(); + }); + + afterEach(function () { + cleanup(); + sinon.restore(); + }); + + it('renders the PR header with title', function () { + const pr = new PullRequestBuilder().build(); + const context = new PRContext(pr); + + const out = render( + + + , + ); + + assert(out.container.querySelector('.title')); + assert(out.container.querySelector('.overview-title')); + }); + + it('applies sticky class when scrolled', function () { + const pr = new PullRequestBuilder().build(); + const context = new PRContext(pr); + + const out = render( + + + , + ); + + const titleElement = out.container.querySelector('.title'); + assert(titleElement); + + // Initial state should not have sticky class + assert(!titleElement.classList.contains('sticky')); + }); +}); From a88fb0b01370172c82b0209615e439c1fbd1cf26 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:00:17 +0000 Subject: [PATCH 06/19] Address code review: add constant and use requestAnimationFrame Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/editorWebview/overview.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/webviews/editorWebview/overview.tsx b/webviews/editorWebview/overview.tsx index 2974e1620b..47a5941c27 100644 --- a/webviews/editorWebview/overview.tsx +++ b/webviews/editorWebview/overview.tsx @@ -29,18 +29,27 @@ const useMediaQuery = (query: string) => { return matches; }; +const STICKY_THRESHOLD = 80; + export const Overview = (pr: PullRequest) => { const isSingleColumnLayout = useMediaQuery('(max-width: 768px)'); const [isSticky, setIsSticky] = React.useState(false); React.useEffect(() => { + let ticking = false; + const handleScroll = () => { - // Make header sticky when scrolled past 80px - const scrolled = window.scrollY > 80; - setIsSticky(scrolled); + if (!ticking) { + window.requestAnimationFrame(() => { + const scrolled = window.scrollY > STICKY_THRESHOLD; + setIsSticky(scrolled); + ticking = false; + }); + ticking = true; + } }; - window.addEventListener('scroll', handleScroll); + window.addEventListener('scroll', handleScroll, { passive: true }); return () => window.removeEventListener('scroll', handleScroll); }, []); From e9e5ee1cfb0cdcfbeba22a81bfbe3a9f7aba0691 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:44:48 +0000 Subject: [PATCH 07/19] Fix flickering at threshold with hysteresis Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- .../vscode.proposed.chatSessionsProvider.d.ts | 5 +++++ webviews/editorWebview/overview.tsx | 14 +++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/@types/vscode.proposed.chatSessionsProvider.d.ts b/src/@types/vscode.proposed.chatSessionsProvider.d.ts index bd4e624430..772fc387b9 100644 --- a/src/@types/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/@types/vscode.proposed.chatSessionsProvider.d.ts @@ -95,6 +95,11 @@ declare module 'vscode' { */ description?: string | MarkdownString; + /** + * An optional badge that provides additional context about the chat session. + */ + badge?: string | MarkdownString; + /** * An optional status indicating the current state of the session. */ diff --git a/webviews/editorWebview/overview.tsx b/webviews/editorWebview/overview.tsx index 47a5941c27..cb371afbae 100644 --- a/webviews/editorWebview/overview.tsx +++ b/webviews/editorWebview/overview.tsx @@ -30,6 +30,7 @@ const useMediaQuery = (query: string) => { }; const STICKY_THRESHOLD = 80; +const STICKY_THRESHOLD_BUFFER = 10; export const Overview = (pr: PullRequest) => { const isSingleColumnLayout = useMediaQuery('(max-width: 768px)'); @@ -41,8 +42,15 @@ export const Overview = (pr: PullRequest) => { const handleScroll = () => { if (!ticking) { window.requestAnimationFrame(() => { - const scrolled = window.scrollY > STICKY_THRESHOLD; - setIsSticky(scrolled); + const scrollY = window.scrollY; + // Use hysteresis to prevent flickering at the threshold + // When not sticky, activate when scrollY > threshold + // When sticky, deactivate when scrollY < (threshold - buffer) + if (!isSticky && scrollY > STICKY_THRESHOLD) { + setIsSticky(true); + } else if (isSticky && scrollY < STICKY_THRESHOLD - STICKY_THRESHOLD_BUFFER) { + setIsSticky(false); + } ticking = false; }); ticking = true; @@ -51,7 +59,7 @@ export const Overview = (pr: PullRequest) => { window.addEventListener('scroll', handleScroll, { passive: true }); return () => window.removeEventListener('scroll', handleScroll); - }, []); + }, [isSticky]); return <>
From 623cbe2f9149d9cc6b3f48d911875578886a7e6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:47:40 +0000 Subject: [PATCH 08/19] Fix useEffect dependency issue with ref Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/editorWebview/overview.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/webviews/editorWebview/overview.tsx b/webviews/editorWebview/overview.tsx index cb371afbae..ce8673231d 100644 --- a/webviews/editorWebview/overview.tsx +++ b/webviews/editorWebview/overview.tsx @@ -35,6 +35,12 @@ const STICKY_THRESHOLD_BUFFER = 10; export const Overview = (pr: PullRequest) => { const isSingleColumnLayout = useMediaQuery('(max-width: 768px)'); const [isSticky, setIsSticky] = React.useState(false); + const isStickyRef = React.useRef(isSticky); + + // Keep ref in sync with state + React.useEffect(() => { + isStickyRef.current = isSticky; + }, [isSticky]); React.useEffect(() => { let ticking = false; @@ -43,12 +49,13 @@ export const Overview = (pr: PullRequest) => { if (!ticking) { window.requestAnimationFrame(() => { const scrollY = window.scrollY; + const currentSticky = isStickyRef.current; // Use hysteresis to prevent flickering at the threshold // When not sticky, activate when scrollY > threshold // When sticky, deactivate when scrollY < (threshold - buffer) - if (!isSticky && scrollY > STICKY_THRESHOLD) { + if (!currentSticky && scrollY > STICKY_THRESHOLD) { setIsSticky(true); - } else if (isSticky && scrollY < STICKY_THRESHOLD - STICKY_THRESHOLD_BUFFER) { + } else if (currentSticky && scrollY < STICKY_THRESHOLD - STICKY_THRESHOLD_BUFFER) { setIsSticky(false); } ticking = false; @@ -59,7 +66,7 @@ export const Overview = (pr: PullRequest) => { window.addEventListener('scroll', handleScroll, { passive: true }); return () => window.removeEventListener('scroll', handleScroll); - }, [isSticky]); + }, []); return <>
From cb46deda336bebfc92566dbed8cf5b754f262c00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 10:08:09 +0000 Subject: [PATCH 09/19] Fix flickering with IntersectionObserver and pure CSS approach Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/components/header.tsx | 29 ++++------- webviews/editorWebview/index.css | 77 +++++++++++++---------------- webviews/editorWebview/overview.tsx | 64 ++++++++++++------------ 3 files changed, 74 insertions(+), 96 deletions(-) diff --git a/webviews/components/header.tsx b/webviews/components/header.tsx index f20d14674b..9418515e19 100644 --- a/webviews/components/header.tsx +++ b/webviews/components/header.tsx @@ -32,9 +32,8 @@ export function Header({ owner, repo, busy, - stateReason, - isCompact -}: PullRequest & { isCompact?: boolean }) { + stateReason +}: PullRequest) { const [currentTitle, setCurrentTitle] = useStateProp(title); const [inEditMode, setEditMode] = useState(false); const codingAgentEvent = mostRecentCopilotEvent(events); @@ -52,16 +51,9 @@ export function Header({ canEdit={canEdit} owner={owner} repo={repo} - isCompact={isCompact} /> - {!isCompact && } +
- {isCompact && ( -
- {getStatus(state, !!isDraft, isIssue, stateReason).icon} - {getStatus(state, !!isDraft, isIssue, stateReason).text} -
- )} +

{' '} @@ -137,16 +128,14 @@ function Title({ title, titleHTML, number, url, inEditMode, setEditMode, setCurr #{number}

- {!isCompact && canEdit ? + {canEdit ? : null} - {!isCompact && ( - - )} +
); diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css index 48e6c6be70..9f13b21b75 100644 --- a/webviews/editorWebview/index.css +++ b/webviews/editorWebview/index.css @@ -48,50 +48,57 @@ textarea:focus, } .title { + position: sticky; + top: 0; + z-index: 100; display: flex; align-items: flex-start; margin: 20px 0 24px; padding-bottom: 24px; border-bottom: 1px solid var(--vscode-list-inactiveSelectionBackground); - transition: all 0.2s ease; + background: var(--vscode-editor-background); } -.title.sticky { - position: sticky; +/* Shadow effect when stuck */ +.title::before { + content: ''; + position: absolute; top: 0; - z-index: 100; + left: -32px; + right: -32px; + bottom: 0; background: var(--vscode-editor-background); - margin: 0; - padding: 8px 0; - border-bottom: 1px solid var(--vscode-panel-border); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + opacity: 0; + transition: opacity 0.2s ease; + pointer-events: none; + z-index: -1; } -.title.sticky .details { - width: 100%; +.title.stuck::before { + opacity: 1; } -.title.sticky .overview-title h2 { - font-size: 18px; - margin: 0; +/* Hide subtitle when stuck */ +.title .subtitle { + transition: opacity 0.2s ease, max-height 0.2s ease; + max-height: 100px; + overflow: hidden; } -.title.sticky .header-actions { - padding-top: 0; +.title.stuck .subtitle { + opacity: 0; + max-height: 0; + pointer-events: none; } -.overview-title.compact { - display: flex; - align-items: center; - gap: 8px; +/* Adjust title size when stuck */ +.title .overview-title h2 { + transition: font-size 0.2s ease; } -.overview-title.compact h2 { +.title.stuck .overview-title h2 { font-size: 18px; - margin: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; } .title .pr-number { @@ -605,15 +612,6 @@ small-button { align-items: center; } -.title.sticky .header-actions { - flex-wrap: nowrap; -} - -.title.sticky .header-actions #status { - margin-right: 0; - flex-shrink: 0; -} - .header-actions>div:first-of-type { flex: 1; } @@ -1261,9 +1259,9 @@ code { border-bottom: 1px solid var(--vscode-contrastBorder); } -.vscode-high-contrast .title.sticky { - border: 1px solid var(--vscode-contrastBorder); +.vscode-high-contrast .title.stuck::before { box-shadow: none; + border: 1px solid var(--vscode-contrastBorder); } .vscode-high-contrast .diff .diffLine { @@ -1292,19 +1290,10 @@ code { padding-bottom: 0px; } - .title.sticky { - padding: 8px 0; - border-bottom: 1px solid var(--vscode-panel-border); - } - - .title.sticky .overview-title h2 { + .title.stuck .overview-title h2 { font-size: 16px; } - .title.sticky .button-group { - flex-wrap: nowrap; - } - #app { display: block; } diff --git a/webviews/editorWebview/overview.tsx b/webviews/editorWebview/overview.tsx index ce8673231d..5ccbc70278 100644 --- a/webviews/editorWebview/overview.tsx +++ b/webviews/editorWebview/overview.tsx @@ -29,49 +29,49 @@ const useMediaQuery = (query: string) => { return matches; }; -const STICKY_THRESHOLD = 80; -const STICKY_THRESHOLD_BUFFER = 10; - export const Overview = (pr: PullRequest) => { const isSingleColumnLayout = useMediaQuery('(max-width: 768px)'); - const [isSticky, setIsSticky] = React.useState(false); - const isStickyRef = React.useRef(isSticky); - - // Keep ref in sync with state - React.useEffect(() => { - isStickyRef.current = isSticky; - }, [isSticky]); + const titleRef = React.useRef(null); + const sentinelRef = React.useRef(null); React.useEffect(() => { - let ticking = false; + const sentinel = sentinelRef.current; + const title = titleRef.current; + + if (!sentinel || !title) { + return; + } - const handleScroll = () => { - if (!ticking) { - window.requestAnimationFrame(() => { - const scrollY = window.scrollY; - const currentSticky = isStickyRef.current; - // Use hysteresis to prevent flickering at the threshold - // When not sticky, activate when scrollY > threshold - // When sticky, deactivate when scrollY < (threshold - buffer) - if (!currentSticky && scrollY > STICKY_THRESHOLD) { - setIsSticky(true); - } else if (currentSticky && scrollY < STICKY_THRESHOLD - STICKY_THRESHOLD_BUFFER) { - setIsSticky(false); - } - ticking = false; - }); - ticking = true; + // Use IntersectionObserver to detect when the sentinel leaves the viewport + // This indicates the title is stuck + const observer = new IntersectionObserver( + ([entry]) => { + // When sentinel is not intersecting, title is stuck + if (entry.isIntersecting) { + title.classList.remove('stuck'); + } else { + title.classList.add('stuck'); + } + }, + { + threshold: [1], + rootMargin: '0px' } - }; + ); + + observer.observe(sentinel); - window.addEventListener('scroll', handleScroll, { passive: true }); - return () => window.removeEventListener('scroll', handleScroll); + return () => { + observer.disconnect(); + }; }, []); return <> -
+ {/* Sentinel element that sits just above the sticky title */} +
+
-
+
{isSingleColumnLayout ? From 515a52c1af01dacb6c6277e43d98173bd7ecb90d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 10:52:01 +0000 Subject: [PATCH 10/19] Fix subtitle visibility and remove side borders/shadows Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/editorWebview/index.css | 35 +++++++------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css index 9f13b21b75..3b3ce738c2 100644 --- a/webviews/editorWebview/index.css +++ b/webviews/editorWebview/index.css @@ -59,44 +59,24 @@ textarea:focus, background: var(--vscode-editor-background); } -/* Shadow effect when stuck */ -.title::before { +/* Shadow effect when stuck - only on bottom */ +.title.stuck::after { content: ''; position: absolute; - top: 0; - left: -32px; - right: -32px; bottom: 0; - background: var(--vscode-editor-background); + left: 0; + right: 0; + height: 1px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - opacity: 0; - transition: opacity 0.2s ease; pointer-events: none; - z-index: -1; -} - -.title.stuck::before { - opacity: 1; } /* Hide subtitle when stuck */ -.title .subtitle { - transition: opacity 0.2s ease, max-height 0.2s ease; - max-height: 100px; - overflow: hidden; -} - .title.stuck .subtitle { - opacity: 0; - max-height: 0; - pointer-events: none; + display: none; } /* Adjust title size when stuck */ -.title .overview-title h2 { - transition: font-size 0.2s ease; -} - .title.stuck .overview-title h2 { font-size: 18px; } @@ -1259,9 +1239,8 @@ code { border-bottom: 1px solid var(--vscode-contrastBorder); } -.vscode-high-contrast .title.stuck::before { +.vscode-high-contrast .title.stuck::after { box-shadow: none; - border: 1px solid var(--vscode-contrastBorder); } .vscode-high-contrast .diff .diffLine { From 53ff457e944ad20eb396cd8b5dd7df62e328030e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 11:07:13 +0000 Subject: [PATCH 11/19] Fix subtitle visibility by adding flex: 1 to .details Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/editorWebview/index.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css index 3b3ce738c2..bcc1f03919 100644 --- a/webviews/editorWebview/index.css +++ b/webviews/editorWebview/index.css @@ -59,6 +59,10 @@ textarea:focus, background: var(--vscode-editor-background); } +.title .details { + flex: 1; +} + /* Shadow effect when stuck - only on bottom */ .title.stuck::after { content: ''; From 3375693d5da45c8642251d330b0a2f251cebf2b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 17:03:59 +0000 Subject: [PATCH 12/19] Fix IntersectionObserver to prevent stuck state on page load Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/editorWebview/overview.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/webviews/editorWebview/overview.tsx b/webviews/editorWebview/overview.tsx index 5ccbc70278..5813c8f8e6 100644 --- a/webviews/editorWebview/overview.tsx +++ b/webviews/editorWebview/overview.tsx @@ -42,11 +42,13 @@ export const Overview = (pr: PullRequest) => { return; } - // Use IntersectionObserver to detect when the sentinel leaves the viewport - // This indicates the title is stuck + // Use IntersectionObserver to detect when the title becomes sticky + // The sentinel is positioned right above the title + // When sentinel scrolls out of view (top of viewport), title becomes stuck const observer = new IntersectionObserver( ([entry]) => { - // When sentinel is not intersecting, title is stuck + // When sentinel is visible, title hasn't become stuck yet + // When sentinel is not visible (scrolled past top), title is stuck if (entry.isIntersecting) { title.classList.remove('stuck'); } else { @@ -54,8 +56,9 @@ export const Overview = (pr: PullRequest) => { } }, { - threshold: [1], - rootMargin: '0px' + // Use rootMargin to trigger slightly before reaching the top + rootMargin: '-1px 0px 0px 0px', + threshold: [1] } ); @@ -67,8 +70,8 @@ export const Overview = (pr: PullRequest) => { }, []); return <> - {/* Sentinel element that sits just above the sticky title */} -
+ {/* Sentinel element positioned just before the sticky title - must have height to be observable */} +
From afd4627a390eb4f15301ac645e3de74e51aecd26 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:54:10 +0000 Subject: [PATCH 13/19] Fix sentinel observer to use threshold 0 without negative rootMargin Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/editorWebview/overview.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/webviews/editorWebview/overview.tsx b/webviews/editorWebview/overview.tsx index 5813c8f8e6..e0bc1e06a8 100644 --- a/webviews/editorWebview/overview.tsx +++ b/webviews/editorWebview/overview.tsx @@ -43,12 +43,12 @@ export const Overview = (pr: PullRequest) => { } // Use IntersectionObserver to detect when the title becomes sticky - // The sentinel is positioned right above the title - // When sentinel scrolls out of view (top of viewport), title becomes stuck + // The sentinel is positioned right before the title + // When sentinel is fully scrolled out of view (past the top), title becomes stuck const observer = new IntersectionObserver( ([entry]) => { - // When sentinel is visible, title hasn't become stuck yet - // When sentinel is not visible (scrolled past top), title is stuck + // When sentinel is intersecting (visible), title is NOT stuck + // When sentinel is not intersecting (scrolled past top), title IS stuck if (entry.isIntersecting) { title.classList.remove('stuck'); } else { @@ -56,9 +56,8 @@ export const Overview = (pr: PullRequest) => { } }, { - // Use rootMargin to trigger slightly before reaching the top - rootMargin: '-1px 0px 0px 0px', - threshold: [1] + // No rootMargin - just detect when sentinel leaves viewport naturally + threshold: [0] } ); @@ -70,7 +69,7 @@ export const Overview = (pr: PullRequest) => { }, []); return <> - {/* Sentinel element positioned just before the sticky title - must have height to be observable */} + {/* Sentinel element positioned just before the sticky title */}
From 863c9f497fe881db7187492371989087d0bf0728 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:22:40 +0000 Subject: [PATCH 14/19] Explicitly remove stuck class on mount and use threshold 1 Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/editorWebview/overview.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/webviews/editorWebview/overview.tsx b/webviews/editorWebview/overview.tsx index e0bc1e06a8..f0b7b96971 100644 --- a/webviews/editorWebview/overview.tsx +++ b/webviews/editorWebview/overview.tsx @@ -42,9 +42,12 @@ export const Overview = (pr: PullRequest) => { return; } + // Initially ensure title is not stuck + title.classList.remove('stuck'); + // Use IntersectionObserver to detect when the title becomes sticky // The sentinel is positioned right before the title - // When sentinel is fully scrolled out of view (past the top), title becomes stuck + // When sentinel scrolls out of view (past the top), title becomes stuck const observer = new IntersectionObserver( ([entry]) => { // When sentinel is intersecting (visible), title is NOT stuck @@ -56,8 +59,9 @@ export const Overview = (pr: PullRequest) => { } }, { - // No rootMargin - just detect when sentinel leaves viewport naturally - threshold: [0] + // Use threshold 1 to only trigger when sentinel is fully visible/invisible + // This prevents false positives when sentinel is partially visible + threshold: [1] } ); From 030cf1ca0e8abb324edf0c9745936ac01c5cc901 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:01:22 +0000 Subject: [PATCH 15/19] Replace IntersectionObserver with scroll-based position detection Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/editorWebview/overview.tsx | 43 ++++++++++++----------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/webviews/editorWebview/overview.tsx b/webviews/editorWebview/overview.tsx index f0b7b96971..27802397fd 100644 --- a/webviews/editorWebview/overview.tsx +++ b/webviews/editorWebview/overview.tsx @@ -32,49 +32,42 @@ const useMediaQuery = (query: string) => { export const Overview = (pr: PullRequest) => { const isSingleColumnLayout = useMediaQuery('(max-width: 768px)'); const titleRef = React.useRef(null); - const sentinelRef = React.useRef(null); React.useEffect(() => { - const sentinel = sentinelRef.current; const title = titleRef.current; - if (!sentinel || !title) { + if (!title) { return; } // Initially ensure title is not stuck title.classList.remove('stuck'); - // Use IntersectionObserver to detect when the title becomes sticky - // The sentinel is positioned right before the title - // When sentinel scrolls out of view (past the top), title becomes stuck - const observer = new IntersectionObserver( - ([entry]) => { - // When sentinel is intersecting (visible), title is NOT stuck - // When sentinel is not intersecting (scrolled past top), title IS stuck - if (entry.isIntersecting) { - title.classList.remove('stuck'); - } else { - title.classList.add('stuck'); - } - }, - { - // Use threshold 1 to only trigger when sentinel is fully visible/invisible - // This prevents false positives when sentinel is partially visible - threshold: [1] + // Use scroll event to detect when title actually becomes sticky + // Check if the title's top position is at the viewport top (sticky position) + const handleScroll = () => { + const rect = title.getBoundingClientRect(); + // Title is stuck when its top is at position 0 (sticky top: 0) + // Add small threshold to account for sub-pixel rendering + if (rect.top <= 1) { + title.classList.add('stuck'); + } else { + title.classList.remove('stuck'); } - ); + }; + + // Check initial state after a brief delay to ensure layout is settled + const timeoutId = setTimeout(handleScroll, 100); - observer.observe(sentinel); + window.addEventListener('scroll', handleScroll, { passive: true }); return () => { - observer.disconnect(); + clearTimeout(timeoutId); + window.removeEventListener('scroll', handleScroll); }; }, []); return <> - {/* Sentinel element positioned just before the sticky title */} -
From 99f5a0c2b49d25acc56a9c08df47e52f6b224dba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:04:28 +0000 Subject: [PATCH 16/19] Add requestAnimationFrame throttling and extract threshold constant Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/editorWebview/overview.tsx | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/webviews/editorWebview/overview.tsx b/webviews/editorWebview/overview.tsx index 27802397fd..e6ac8a4471 100644 --- a/webviews/editorWebview/overview.tsx +++ b/webviews/editorWebview/overview.tsx @@ -43,16 +43,25 @@ export const Overview = (pr: PullRequest) => { // Initially ensure title is not stuck title.classList.remove('stuck'); - // Use scroll event to detect when title actually becomes sticky + // Small threshold to account for sub-pixel rendering + const STICKY_THRESHOLD = 1; + + // Use scroll event with requestAnimationFrame to detect when title becomes sticky // Check if the title's top position is at the viewport top (sticky position) + let ticking = false; const handleScroll = () => { - const rect = title.getBoundingClientRect(); - // Title is stuck when its top is at position 0 (sticky top: 0) - // Add small threshold to account for sub-pixel rendering - if (rect.top <= 1) { - title.classList.add('stuck'); - } else { - title.classList.remove('stuck'); + if (!ticking) { + window.requestAnimationFrame(() => { + const rect = title.getBoundingClientRect(); + // Title is stuck when its top is at position 0 (sticky top: 0) + if (rect.top <= STICKY_THRESHOLD) { + title.classList.add('stuck'); + } else { + title.classList.remove('stuck'); + } + ticking = false; + }); + ticking = true; } }; From 8c2d4bba828f981380ff78fd6ac4558843f08ad6 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:36:03 +0100 Subject: [PATCH 17/19] reduce diff --- src/@types/vscode.proposed.chatParticipantAdditions.d.ts | 1 - src/@types/vscode.proposed.chatSessionsProvider.d.ts | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index aa7001a3d2..71520fa1ec 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -105,7 +105,6 @@ declare module 'vscode' { isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; fromSubAgent?: boolean; - presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); } diff --git a/src/@types/vscode.proposed.chatSessionsProvider.d.ts b/src/@types/vscode.proposed.chatSessionsProvider.d.ts index 772fc387b9..bd4e624430 100644 --- a/src/@types/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/@types/vscode.proposed.chatSessionsProvider.d.ts @@ -95,11 +95,6 @@ declare module 'vscode' { */ description?: string | MarkdownString; - /** - * An optional badge that provides additional context about the chat session. - */ - badge?: string | MarkdownString; - /** * An optional status indicating the current state of the session. */ From 0d5f9a81b49938860fde1be64a06d1f2f5da1f24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:38:52 +0000 Subject: [PATCH 18/19] Hide edit title button when in sticky mode Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/editorWebview/index.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css index bcc1f03919..4cf451c172 100644 --- a/webviews/editorWebview/index.css +++ b/webviews/editorWebview/index.css @@ -80,6 +80,11 @@ textarea:focus, display: none; } +/* Hide edit button when stuck */ +.title.stuck .overview-title button[title="Rename"] { + display: none; +} + /* Adjust title size when stuck */ .title.stuck .overview-title h2 { font-size: 18px; From 86aa087ea5e94099d7d5c223b24044377e206994 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:01:33 +0000 Subject: [PATCH 19/19] Use ID instead of title attribute for edit button selector Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/common/constants.ts | 5 +++++ webviews/components/header.tsx | 3 ++- webviews/editorWebview/index.css | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/webviews/common/constants.ts b/webviews/common/constants.ts index f556c0be96..55a0367153 100644 --- a/webviews/common/constants.ts +++ b/webviews/common/constants.ts @@ -7,3 +7,8 @@ * ID for the main comment textarea element in the PR description page. */ export const COMMENT_TEXTAREA_ID = 'comment-textarea'; + +/** + * ID for the edit title button in the PR/Issue header. + */ +export const EDIT_TITLE_BUTTON_ID = 'edit-title-button'; diff --git a/webviews/components/header.tsx b/webviews/components/header.tsx index 9418515e19..7b65839d49 100644 --- a/webviews/components/header.tsx +++ b/webviews/components/header.tsx @@ -11,6 +11,7 @@ import { copilotEventToStatus, CopilotPRStatus, mostRecentCopilotEvent } from '. import { CopilotStartedEvent, TimelineEvent } from '../../src/common/timelineEvent'; import { GithubItemStateEnum, StateReason } from '../../src/github/interface'; import { CodingAgentContext, OverviewContext, PullRequest } from '../../src/github/views'; +import { EDIT_TITLE_BUTTON_ID } from '../common/constants'; import PullRequestContext from '../common/context'; import { useStateProp } from '../common/hooks'; @@ -129,7 +130,7 @@ function Title({ title, titleHTML, number, url, inEditMode, setEditMode, setCurr {canEdit ? - : null} diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css index 670d3bd376..1f05a8eb88 100644 --- a/webviews/editorWebview/index.css +++ b/webviews/editorWebview/index.css @@ -81,7 +81,7 @@ textarea:focus, } /* Hide edit button when stuck */ -.title.stuck .overview-title button[title="Rename"] { +.title.stuck #edit-title-button { display: none; }