Sessions: Enhance collapsed sidebar widget with session status indicators and panel toggle button (#303521)

* Enhance collapsed sidebar widget with session status indicators and panel toggle button

* Refactor sidebar toggle button and update CSS class for session status

Co-authored-by: Copilot <copilot@github.com>

---------

Co-authored-by: mrleemurray <mrleemurray@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Lee Murray
2026-03-20 16:51:41 +00:00
committed by GitHub
parent 7dcd2c9178
commit c99f8109a6
2 changed files with 128 additions and 57 deletions

View File

@@ -44,11 +44,16 @@ export class CollapsedSidebarWidget extends Disposable {
super();
this.element = append(parent, $('.collapsed-panel-widget.collapsed-sidebar-widget'));
this.indicatorContainer = append(this.element, $('.collapsed-panel-buttons'));
// New session button
// Sidebar toggle button (leftmost)
this._register(this.createSidebarToggleButton());
// New session button (next to panel toggle)
this._register(this.createNewSessionButton());
// Session status indicators (rightmost)
this.indicatorContainer = append(this.element, $('.collapsed-panel-button.collapsed-sidebar-status'));
// Listen for session changes
this._register(this.agentSessionsService.model.onDidChangeSessions(() => this.rebuildIndicators()));
@@ -72,6 +77,35 @@ export class CollapsedSidebarWidget extends Disposable {
return store;
}
private createSidebarToggleButton(): DisposableStore {
const store = new DisposableStore();
const btn = append(this.element, $('.collapsed-panel-button.collapsed-sidebar-panel-toggle'));
let iconElement: HTMLElement | undefined;
const updateIcon = () => {
const sidebarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART);
const icon = sidebarVisible ? Codicon.layoutSidebarLeft : Codicon.layoutSidebarLeftOff;
iconElement?.remove();
iconElement = append(btn, $(ThemeIcon.asCSSSelector(icon)));
};
updateIcon();
store.add(this.hoverService.setupManagedHover(this.hoverDelegate, btn, localize('toggleSidebar', "Toggle Side Bar")));
store.add(addDisposableListener(btn, EventType.CLICK, () => {
this.commandService.executeCommand('workbench.action.agentToggleSidebarVisibility');
}));
store.add(this.layoutService.onDidChangePartVisibility(e => {
if (e.partId === Parts.SIDEBAR_PART) {
updateIcon();
}
}));
return store;
}
private rebuildIndicators(): void {
this.indicatorDisposables.clear();
this.indicatorContainer.textContent = '';
@@ -79,75 +113,61 @@ export class CollapsedSidebarWidget extends Disposable {
const sessions = this.agentSessionsService.model.sessions;
const counts = this.countSessionsByStatus(sessions);
// In-progress indicator
const tooltipParts: string[] = [];
// In-progress (matches agentSessionsViewer: sessionInProgress)
if (counts.inProgress > 0) {
this.createIndicator(
Codicon.loading,
`${counts.inProgress}`,
localize('sessionsInProgress', "{0} session(s) in progress", counts.inProgress),
'collapsed-sidebar-indicator-active'
);
this.appendStatusSegment(Codicon.sessionInProgress, `${counts.inProgress}`, 'collapsed-sidebar-indicator-active');
tooltipParts.push(localize('sessionsInProgress', "{0} session(s) in progress", counts.inProgress));
}
// Needs input indicator
// Needs input (matches agentSessionsViewer: circleFilled)
if (counts.needsInput > 0) {
this.createIndicator(
Codicon.bell,
`${counts.needsInput}`,
localize('sessionsNeedInput', "{0} session(s) need input", counts.needsInput),
'collapsed-sidebar-indicator-input'
);
this.appendStatusSegment(Codicon.circleFilled, `${counts.needsInput}`, 'collapsed-sidebar-indicator-input');
tooltipParts.push(localize('sessionsNeedInput', "{0} session(s) need input", counts.needsInput));
}
// Error indicator
// Failed (matches agentSessionsViewer: error)
if (counts.failed > 0) {
this.createIndicator(
Codicon.error,
`${counts.failed}`,
localize('sessionsFailed', "{0} session(s) with errors", counts.failed),
'collapsed-sidebar-indicator-error'
);
this.appendStatusSegment(Codicon.error, `${counts.failed}`, 'collapsed-sidebar-indicator-error');
tooltipParts.push(localize('sessionsFailed', "{0} session(s) with errors", counts.failed));
}
// Completed indicator
if (counts.completed > 0) {
this.createIndicator(
Codicon.check,
`${counts.completed}`,
localize('sessionsCompleted', "{0} session(s) completed", counts.completed),
'collapsed-sidebar-indicator-done'
);
// Unread (matches agentSessionsViewer: circleFilled with textLink-foreground)
if (counts.unread > 0) {
this.appendStatusSegment(Codicon.circleFilled, `${counts.unread}`, 'collapsed-sidebar-indicator-unread');
tooltipParts.push(localize('sessionsUnread', "{0} unread session(s)", counts.unread));
}
// If no sessions at all, show a total count
// If no sessions at all
if (sessions.length === 0) {
this.createIndicator(
Codicon.commentDiscussion,
'0',
localize('noSessions', "No sessions"),
'collapsed-sidebar-indicator-empty'
);
this.appendStatusSegment(Codicon.commentDiscussion, '0', 'collapsed-sidebar-indicator-empty');
tooltipParts.push(localize('noSessions', "No sessions"));
}
if (tooltipParts.length > 0) {
this.indicatorDisposables.add(this.hoverService.setupManagedHover(
this.hoverDelegate, this.indicatorContainer, tooltipParts.join('\n')
));
this.indicatorDisposables.add(addDisposableListener(this.indicatorContainer, EventType.CLICK, () => {
this.layoutService.setPartHidden(false, Parts.SIDEBAR_PART);
}));
}
}
private createIndicator(icon: ThemeIcon, count: string, tooltip: string, className: string): void {
const indicator = append(this.indicatorContainer, $(`.collapsed-panel-button.${className}`));
append(indicator, $(ThemeIcon.asCSSSelector(icon)));
const label = append(indicator, $('span.collapsed-sidebar-count'));
private appendStatusSegment(icon: ThemeIcon, count: string, className: string): void {
const segment = append(this.indicatorContainer, $(`span.collapsed-sidebar-segment.${className}`));
append(segment, $(ThemeIcon.asCSSSelector(icon)));
const label = append(segment, $('span.collapsed-sidebar-count'));
label.textContent = count;
this.indicatorDisposables.add(this.hoverService.setupManagedHover(this.hoverDelegate, indicator, tooltip));
this.indicatorDisposables.add(addDisposableListener(indicator, EventType.CLICK, () => {
this.layoutService.setPartHidden(false, Parts.SIDEBAR_PART);
}));
}
private countSessionsByStatus(sessions: IAgentSession[]): { inProgress: number; needsInput: number; failed: number; completed: number } {
private countSessionsByStatus(sessions: IAgentSession[]): { inProgress: number; needsInput: number; failed: number; unread: number } {
let inProgress = 0;
let needsInput = 0;
let failed = 0;
let completed = 0;
let unread = 0;
for (const session of sessions) {
if (session.isArchived()) {
@@ -164,12 +184,14 @@ export class CollapsedSidebarWidget extends Disposable {
failed++;
break;
case AgentSessionStatus.Completed:
completed++;
if (!session.isRead()) {
unread++;
}
break;
}
}
return { inProgress, needsInput, failed, completed };
return { inProgress, needsInput, failed, unread };
}
show(): void {

View File

@@ -74,6 +74,22 @@
color: inherit;
}
/* ---- Consolidated session status button ---- */
.agent-sessions-workbench .collapsed-panel-button.collapsed-sidebar-status {
gap: 6px;
}
.agent-sessions-workbench .collapsed-sidebar-segment {
display: inline-flex;
align-items: center;
gap: 2px;
}
.agent-sessions-workbench .collapsed-panel-button .collapsed-sidebar-segment .codicon {
font-size: 14px;
}
/* ---- Sidebar indicators ---- */
.agent-sessions-workbench .collapsed-sidebar-count,
@@ -84,16 +100,49 @@
color: inherit;
}
.agent-sessions-workbench .collapsed-sidebar-indicator-active .codicon {
animation: codicon-spin 1.5s infinite linear;
.agent-sessions-workbench .collapsed-sidebar-segment.collapsed-sidebar-indicator-active .codicon {
color: var(--vscode-textLink-foreground);
}
.agent-sessions-workbench .collapsed-sidebar-indicator-error {
.agent-sessions-workbench .collapsed-sidebar-segment.collapsed-sidebar-indicator-error {
color: var(--vscode-errorForeground);
}
.agent-sessions-workbench .collapsed-sidebar-indicator-input {
color: var(--vscode-notificationsInfoIcon-foreground);
.agent-sessions-workbench .collapsed-sidebar-segment.collapsed-sidebar-indicator-input {
color: var(--vscode-list-warningForeground);
}
.agent-sessions-workbench .collapsed-sidebar-segment.collapsed-sidebar-indicator-input .codicon {
animation: collapsed-sidebar-needs-input-pulse 2s ease-in-out infinite;
}
@keyframes collapsed-sidebar-needs-input-pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.4;
}
}
@media (prefers-reduced-motion: reduce) {
.agent-sessions-workbench .collapsed-sidebar-segment.collapsed-sidebar-indicator-input .codicon {
animation: none;
}
}
.agent-sessions-workbench .collapsed-sidebar-segment.collapsed-sidebar-indicator-unread {
color: var(--vscode-textLink-foreground);
}
/* ---- Panel toggle button ---- */
.agent-sessions-workbench .collapsed-sidebar-panel-toggle {
opacity: 0.7;
}
.agent-sessions-workbench .collapsed-sidebar-panel-toggle:hover {
opacity: 1;
}
/* ---- Auxiliary bar indicators ---- */