feat(context-panel): add plan view to sidebar panel (#446)

This commit is contained in:
Nelson Pires
2026-02-19 00:52:18 +02:00
committed by GitHub
parent 057bdb584c
commit 34c8ff3962
3 changed files with 72 additions and 4 deletions
@@ -2,7 +2,7 @@ import React from 'react';
import { RiCloseLine, RiFullscreenExitLine, RiFullscreenLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { DiffView, FilesView } from '@/components/views';
import { DiffView, FilesView, PlanView } from '@/components/views';
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
import { cn } from '@/lib/utils';
import { useFilesViewTabsStore } from '@/stores/useFilesViewTabsStore';
@@ -145,7 +145,7 @@ export const ContextPanel: React.FC = () => {
const activeFilePath = useFilesViewTabsStore((state) => (directoryKey ? (state.byRoot[directoryKey]?.selectedPath ?? null) : null));
const panelTitle = panelState?.mode === 'diff' ? 'Diff' : panelState?.mode === 'file' ? 'File' : panelState?.mode === 'context' ? 'Context' : 'Panel';
const panelTitle = panelState?.mode === 'diff' ? 'Diff' : panelState?.mode === 'file' ? 'File' : panelState?.mode === 'context' ? 'Context' : panelState?.mode === 'plan' ? 'Plan' : 'Panel';
const effectivePath = panelState?.mode === 'file' ? (activeFilePath ?? panelState?.targetPath ?? null) : panelState?.mode === 'context' ? null : (panelState?.targetPath ?? null);
const pathLabel = getRelativePathLabel(effectivePath, effectiveDirectory);
@@ -155,7 +155,9 @@ export const ContextPanel: React.FC = () => {
? <FilesView mode="editor-only" />
: panelState?.mode === 'context'
? <ContextPanelContent />
: null;
: panelState?.mode === 'plan'
? <PlanView />
: null;
const header = (
<header className="flex h-10 items-center gap-2 border-b border-border/40 px-2.5">
@@ -142,6 +142,7 @@ export const Header: React.FC = () => {
const toggleBottomTerminal = useUIStore((state) => state.toggleBottomTerminal);
const toggleRightSidebar = useUIStore((state) => state.toggleRightSidebar);
const openContextOverview = useUIStore((state) => state.openContextOverview);
const openContextPlan = useUIStore((state) => state.openContextPlan);
const closeContextPanel = useUIStore((state) => state.closeContextPanel);
const contextPanelByDirectory = useUIStore((state) => state.contextPanelByDirectory);
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
@@ -917,6 +918,30 @@ export const Header: React.FC = () => {
return Boolean(panelState?.isOpen && panelState.mode === 'context');
}, [contextPanelByDirectory, openDirectory]);
const handleOpenContextPlan = React.useCallback(() => {
const directory = normalize(openDirectory || '');
if (!directory) {
return;
}
const panelState = contextPanelByDirectory[directory];
if (panelState?.isOpen && panelState.mode === 'plan') {
closeContextPanel(directory);
return;
}
openContextPlan(directory);
}, [closeContextPanel, contextPanelByDirectory, openContextPlan, openDirectory]);
const isContextPlanActive = React.useMemo(() => {
const directory = normalize(openDirectory || '');
if (!directory) {
return false;
}
const panelState = contextPanelByDirectory[directory];
return Boolean(panelState?.isOpen && panelState.mode === 'plan');
}, [contextPanelByDirectory, openDirectory]);
const headerIconButtonClass = 'app-region-no-drag inline-flex h-9 w-9 items-center justify-center gap-2 p-2 rounded-md typography-ui-label font-medium text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50 hover:text-foreground hover:bg-interactive-hover transition-colors';
const desktopPaddingClass = React.useMemo(() => {
@@ -1644,6 +1669,23 @@ export const Header: React.FC = () => {
percentIconClassName="h-5 w-5"
/>
)}
{showPlanTab && (
<Tooltip delayDuration={500}>
<TooltipTrigger asChild>
<button
type="button"
aria-label="Open plan"
onClick={handleOpenContextPlan}
className={cn(headerIconButtonClass, isContextPlanActive && 'bg-[var(--interactive-hover)] text-foreground')}
>
<RiFileTextLine className="h-5 w-5" />
</button>
</TooltipTrigger>
<TooltipContent>
<p>Plan</p>
</TooltipContent>
</Tooltip>
)}
<OpenInAppButton directory={openDirectory} className="mr-1" />
<DropdownMenu
open={isDesktopServicesOpen}
+25 -1
View File
@@ -6,7 +6,7 @@ import { SEMANTIC_TYPOGRAPHY, getTypographyVariable, type SemanticTypographyKey
export type MainTab = 'chat' | 'plan' | 'git' | 'diff' | 'terminal' | 'files';
export type RightSidebarTab = 'git' | 'files';
export type ContextPanelMode = 'diff' | 'file' | 'context';
export type ContextPanelMode = 'diff' | 'file' | 'context' | 'plan';
type ContextPanelDirectoryState = {
isOpen: boolean;
@@ -225,6 +225,7 @@ interface UIStore {
openContextDiff: (directory: string, filePath: string) => void;
openContextFile: (directory: string, filePath: string) => void;
openContextOverview: (directory: string) => void;
openContextPlan: (directory: string) => void;
closeContextPanel: (directory: string) => void;
toggleContextPanelExpanded: (directory: string) => void;
setContextPanelWidth: (directory: string, width: number) => void;
@@ -549,6 +550,29 @@ export const useUIStore = create<UIStore>()(
});
},
openContextPlan: (directory) => {
const normalizedDirectory = normalizeDirectoryPath((directory || '').trim());
if (!normalizedDirectory) {
return;
}
set((state) => {
const prev = state.contextPanelByDirectory[normalizedDirectory];
const current = touchContextPanelState(prev);
const byDirectory = {
...state.contextPanelByDirectory,
[normalizedDirectory]: {
...current,
isOpen: true,
mode: 'plan' as const,
targetPath: null,
},
};
return { contextPanelByDirectory: clampContextPanelRoots(byDirectory, 20) };
});
},
closeContextPanel: (directory) => {
const normalizedDirectory = normalizeDirectoryPath((directory || '').trim());
if (!normalizedDirectory) {