mirror of
https://github.com/coder/coder.git
synced 2026-06-07 23:18:20 +00:00
fix(agents): persist right panel open/closed state to localStorage (#22906)
Removes the auto-open/close behavior that would force the right-side panel open whenever diff status or git repository data appeared. Instead, the panel's visibility is now persisted via the `agents.right-panel-open` localStorage key (matching the existing `agents.right-panel-width` pattern for the panel width). This gives users a consistent UX when switching between chats — the panel stays in whatever state they last set it to. ## Changes - **Removed** two auto-open blocks in `AgentDetailView` that tracked `prevHasDiffStatus` / `prevHasGitRepos` and forced `showSidebarPanel = true` - **Added** `localStorage` persistence for the panel open/closed state under key `agents.right-panel-open` - Initial state is read from localStorage on mount (defaults to closed) - Every toggle/close writes through to localStorage via `handleSetShowSidebarPanel` - Panel width was already persisted via `agents.right-panel-width` in `RightPanel.tsx` — no changes needed there
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
||||
reactRouterParameters,
|
||||
} from "storybook-addon-remix-react-router";
|
||||
import AgentDetail from "./AgentDetail";
|
||||
import { RIGHT_PANEL_OPEN_KEY } from "./AgentDetailView";
|
||||
import type { AgentsOutletContext } from "./AgentsPage";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -305,6 +306,10 @@ export const Loading: Story = {};
|
||||
|
||||
/** Full layout with actions menu and diff panel portaled to the right slot. */
|
||||
export const CompletedWithDiffPanel: Story = {
|
||||
beforeEach: () => {
|
||||
localStorage.setItem(RIGHT_PANEL_OPEN_KEY, "true");
|
||||
return () => localStorage.removeItem(RIGHT_PANEL_OPEN_KEY);
|
||||
},
|
||||
parameters: {
|
||||
queries: buildQueries(
|
||||
{
|
||||
@@ -517,6 +522,10 @@ export const StreamedSubagentTitle: Story = {
|
||||
* the PR tab.
|
||||
*/
|
||||
export const SidebarWithPRAndRepos: Story = {
|
||||
beforeEach: () => {
|
||||
localStorage.setItem(RIGHT_PANEL_OPEN_KEY, "true");
|
||||
return () => localStorage.removeItem(RIGHT_PANEL_OPEN_KEY);
|
||||
},
|
||||
parameters: {
|
||||
queries: buildQueries(
|
||||
{
|
||||
@@ -697,6 +706,10 @@ export const SidebarWithPRAndRepos: Story = {
|
||||
* the tab bar is hidden and the repo panel is rendered directly.
|
||||
*/
|
||||
export const SidebarWithSingleRepo: Story = {
|
||||
beforeEach: () => {
|
||||
localStorage.setItem(RIGHT_PANEL_OPEN_KEY, "true");
|
||||
return () => localStorage.removeItem(RIGHT_PANEL_OPEN_KEY);
|
||||
},
|
||||
parameters: {
|
||||
queries: buildQueries(
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
AgentDetailLoadingView,
|
||||
AgentDetailNotFoundView,
|
||||
AgentDetailView,
|
||||
RIGHT_PANEL_OPEN_KEY,
|
||||
} from "./AgentDetailView";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -191,6 +192,10 @@ export const SubmissionPending: Story = {
|
||||
|
||||
/** Right sidebar panel is open with diff status data. */
|
||||
export const WithSidebarPanel: Story = {
|
||||
beforeEach: () => {
|
||||
localStorage.setItem(RIGHT_PANEL_OPEN_KEY, "true");
|
||||
return () => localStorage.removeItem(RIGHT_PANEL_OPEN_KEY);
|
||||
},
|
||||
args: {
|
||||
prNumber: 123,
|
||||
diffStatusData: {
|
||||
|
||||
@@ -3,7 +3,7 @@ import type * as TypesGen from "api/typesGenerated";
|
||||
import type { ModelSelectorOption } from "components/ai-elements";
|
||||
import { Skeleton } from "components/Skeleton/Skeleton";
|
||||
import { ArchiveIcon } from "lucide-react";
|
||||
import { type FC, type RefObject, useMemo, useState } from "react";
|
||||
import { type FC, type RefObject, useCallback, useMemo, useState } from "react";
|
||||
import type { UrlTransform } from "streamdown";
|
||||
import { cn } from "utils/cn";
|
||||
import { pageTitle } from "utils/page";
|
||||
@@ -115,6 +115,9 @@ interface AgentDetailViewProps {
|
||||
urlTransform?: UrlTransform;
|
||||
}
|
||||
|
||||
/** localStorage key controlling whether the right panel is visible. */
|
||||
export const RIGHT_PANEL_OPEN_KEY = "agents.right-panel-open";
|
||||
|
||||
export const AgentDetailView: FC<AgentDetailViewProps> = ({
|
||||
agentId,
|
||||
chatTitle,
|
||||
@@ -160,38 +163,30 @@ export const AgentDetailView: FC<AgentDetailViewProps> = ({
|
||||
urlTransform,
|
||||
}) => {
|
||||
// Panel/sidebar UI state – purely visual, no data-fetching
|
||||
// implications.
|
||||
const [showSidebarPanel, setShowSidebarPanel] = useState(false);
|
||||
// implications. The open/closed state is persisted to localStorage
|
||||
// so users get a consistent layout when switching between chats.
|
||||
const [showSidebarPanel, setShowSidebarPanel] = useState(() => {
|
||||
if (typeof window === "undefined") return false;
|
||||
return localStorage.getItem(RIGHT_PANEL_OPEN_KEY) === "true";
|
||||
});
|
||||
const handleSetShowSidebarPanel = useCallback(
|
||||
(next: boolean | ((prev: boolean) => boolean)) => {
|
||||
setShowSidebarPanel((prev) => {
|
||||
const value = typeof next === "function" ? next(prev) : next;
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.setItem(RIGHT_PANEL_OPEN_KEY, String(value));
|
||||
}
|
||||
return value;
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
const [isRightPanelExpanded, setIsRightPanelExpanded] = useState(false);
|
||||
const [dragVisualExpanded, setDragVisualExpanded] = useState<boolean | null>(
|
||||
null,
|
||||
);
|
||||
const visualExpanded = dragVisualExpanded ?? isRightPanelExpanded;
|
||||
|
||||
// Derive trivial booleans the View can compute itself.
|
||||
const hasDiffStatus = Boolean(diffStatusData?.url);
|
||||
const hasGitRepos = gitWatcher.repositories.size > 0;
|
||||
|
||||
// Auto-open the diff panel when diff status first appears.
|
||||
// See: https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes
|
||||
const [prevHasDiffStatus, setPrevHasDiffStatus] = useState(false);
|
||||
if (hasDiffStatus !== prevHasDiffStatus) {
|
||||
setPrevHasDiffStatus(hasDiffStatus);
|
||||
if (hasDiffStatus && !window.matchMedia("(max-width: 767px)").matches) {
|
||||
setShowSidebarPanel(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-open sidebar when git watcher receives its first non-empty
|
||||
// repositories update.
|
||||
const [prevHasGitRepos, setPrevHasGitRepos] = useState(false);
|
||||
if (hasGitRepos !== prevHasGitRepos) {
|
||||
setPrevHasGitRepos(hasGitRepos);
|
||||
if (hasGitRepos && !window.matchMedia("(max-width: 767px)").matches) {
|
||||
setShowSidebarPanel(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute local diff stats from git watcher unified diffs.
|
||||
const localDiffStats = useMemo(() => {
|
||||
let additions = 0;
|
||||
@@ -239,7 +234,7 @@ export const AgentDetailView: FC<AgentDetailViewProps> = ({
|
||||
onOpenParentChat={(chatId) => onNavigateToChat(chatId)}
|
||||
panel={{
|
||||
showSidebarPanel,
|
||||
onToggleSidebar: () => setShowSidebarPanel((prev) => !prev),
|
||||
onToggleSidebar: () => handleSetShowSidebarPanel((prev) => !prev),
|
||||
}}
|
||||
workspace={{
|
||||
canOpenEditors,
|
||||
@@ -326,7 +321,7 @@ export const AgentDetailView: FC<AgentDetailViewProps> = ({
|
||||
isOpen={shouldShowSidebar}
|
||||
isExpanded={isRightPanelExpanded}
|
||||
onToggleExpanded={() => setIsRightPanelExpanded((prev) => !prev)}
|
||||
onClose={() => setShowSidebarPanel(false)}
|
||||
onClose={() => handleSetShowSidebarPanel(false)}
|
||||
onVisualExpandedChange={setDragVisualExpanded}
|
||||
isSidebarCollapsed={isSidebarCollapsed}
|
||||
onToggleSidebarCollapsed={onToggleSidebarCollapsed}
|
||||
@@ -354,7 +349,7 @@ export const AgentDetailView: FC<AgentDetailViewProps> = ({
|
||||
),
|
||||
},
|
||||
]}
|
||||
onClose={() => setShowSidebarPanel(false)}
|
||||
onClose={() => handleSetShowSidebarPanel(false)}
|
||||
isExpanded={visualExpanded}
|
||||
onToggleExpanded={() => setIsRightPanelExpanded((prev) => !prev)}
|
||||
isSidebarCollapsed={isSidebarCollapsed}
|
||||
|
||||
Reference in New Issue
Block a user