Expand agent feedback widget when revealing feedback (#323687)

* Expand agent feedback widget when revealing feedback

revealFeedback set the navigation anchor to the raw feedback id, but the
editor widget contribution matches against the prefixed session-editor-
comment id. As a result the editor scrolled to the location but the widget
stayed collapsed. Convert the feedback id to the session-editor-comment id
so the matching widget expands.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix test wrapper, comment length and mock typing from review

- Reintroduce the 'removing feedback preserves ordering' test() wrapper that
  was accidentally dropped, fixing the TS1128 compile error breaking CI.
- Compress the revealFeedback inline comment to a single line.
- Use unknown/undefined instead of any in the openEditor test stub.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Break import cycle flagged by cyclic dependency check

sessionEditorComments.ts only uses AgentFeedbackKind/State and IAgentFeedback,
which are defined in agentFeedbackModel.ts (the service merely re-exports them).
Import them directly from the model so agentFeedbackService.ts can import
sessionEditorComments.ts without forming a cycle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Benjamin Christopher Simmonds
2026-06-30 12:41:15 +02:00
committed by GitHub
parent 5a56ea1ac1
commit 321f6bcdc0
3 changed files with 25 additions and 2 deletions

View File

@@ -27,6 +27,7 @@ import { isAgentHostProviderId } from '../../../common/agentHostSessionsProvider
import { AnnotationsAgentFeedbackItemsBackend, IAgentFeedbackItemsBackend, InMemoryAgentFeedbackItemsBackend } from './agentFeedbackItemsBackend.js';
import { ATTACHMENT_ID_PREFIX, createAgentFeedbackVariableEntry } from './agentFeedbackAttachmentEntry.js';
import { AgentFeedbackKind, AgentFeedbackState, type IAgentFeedback } from './agentFeedbackModel.js';
import { SessionEditorCommentSource, toSessionEditorCommentId } from './sessionEditorComments.js';
// --- Types --------------------------------------------------------------------
@@ -549,7 +550,8 @@ export class AgentFeedbackService extends Disposable implements IAgentFeedbackSe
if (!feedback) {
return;
}
await this.revealSessionComment(sessionResource, feedbackId, feedback.resourceUri, feedback.range);
// Anchor using the session-editor-comment id (not the raw feedback id) so the editor widget contribution matches the active item and expands its widget.
await this.revealSessionComment(sessionResource, toSessionEditorCommentId(SessionEditorCommentSource.AgentFeedback, feedbackId), feedback.resourceUri, feedback.range);
}
async revealSessionComment(sessionResource: URI, commentId: string, resourceUri: URI, range: IRange): Promise<void> {

View File

@@ -5,7 +5,7 @@
import { IRange, Range } from '../../../../editor/common/core/range.js';
import { URI } from '../../../../base/common/uri.js';
import { AgentFeedbackKind, AgentFeedbackState, IAgentFeedback } from './agentFeedbackService.js';
import { AgentFeedbackKind, AgentFeedbackState, IAgentFeedback } from './agentFeedbackModel.js';
import { ICodeReviewSuggestion, IPRReviewComment, IPRReviewState, PRReviewStateKind } from '../../codeReview/browser/codeReviewService.js';
export const enum SessionEditorCommentSource {

View File

@@ -12,6 +12,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/tes
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
import { mock } from '../../../../../base/test/common/mock.js';
import { AgentFeedbackKind, AgentFeedbackService, AgentFeedbackState, IAgentFeedbackService } from '../../browser/agentFeedbackService.js';
import { getSessionEditorComments } from '../../browser/sessionEditorComments.js';
import { IChatEditingService } from '../../../../../workbench/contrib/chat/common/editing/chatEditingService.js';
import { IChatWidget, IChatWidgetService } from '../../../../../workbench/contrib/chat/browser/chat.js';
import { IAgentFeedbackVariableEntry } from '../../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js';
@@ -51,6 +52,7 @@ suite('AgentFeedbackService - Ordering', () => {
instantiationService.stub(IEditorService, new class extends mock<IEditorService>() {
override onDidVisibleEditorsChange = Event.None;
override visibleEditorPanes = [];
override openEditor(..._args: unknown[]): Promise<undefined> { return Promise.resolve(undefined); }
});
instantiationService.stub(ISessionsManagementService, new class extends mock<ISessionsManagementService>() {
override getSession(_resource: URI) { return undefined; }
@@ -191,6 +193,25 @@ suite('AgentFeedbackService - Ordering', () => {
assert.strictEqual(bearing.activeIdx, 2);
});
test('revealFeedback anchors the matching session editor comment so its widget expands', async () => {
const f1 = service.addFeedback(session, fileA, r(5), 'A:5');
const f2 = service.addFeedback(session, fileA, r(20), 'A:20');
// The editor widget contribution expands the widget whose session
// editor comment matches the navigation anchor. revealFeedback must set
// the anchor using the prefixed session-editor-comment id (not the raw
// feedback id) for that match to succeed.
await service.revealFeedback(session, f2.id);
const comments = getSessionEditorComments(session, service.getFeedback(session));
const bearing = service.getNavigationBearing(session, comments);
assert.strictEqual(comments[bearing.activeIdx]?.sourceId, f2.id);
await service.revealFeedback(session, f1.id);
const bearingAfter = service.getNavigationBearing(session, comments);
assert.strictEqual(comments[bearingAfter.activeIdx]?.sourceId, f1.id);
});
test('removing feedback preserves ordering', () => {
const f1 = service.addFeedback(session, fileA, r(30), 'A:30');
service.addFeedback(session, fileA, r(10), 'A:10');