Skip to content

Commit 1dc11e6

Browse files
authored
🤖 Persist FileTree expand/collapse state per workspace (#336)
1 parent af32a5b commit 1dc11e6

File tree

15 files changed

+88
-28
lines changed

15 files changed

+88
-28
lines changed

scripts/generate_prism_css.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ for (const [key, value] of Object.entries(vscDarkPlus as Record<string, unknown>
2525
}
2626

2727
// Convert CSS properties object to CSS string
28-
function cssPropertiesToString(props: CSSProperties): string {
29-
return Object.entries(props)
28+
function cssPropertiesToString(props: CSSProperties, selector: string): string {
29+
const entries = Object.entries(props)
3030
.filter(([key]) => {
3131
// Skip font-family and font-size - we want to inherit these
3232
return key !== "fontFamily" && key !== "fontSize";
@@ -35,8 +35,14 @@ function cssPropertiesToString(props: CSSProperties): string {
3535
// Convert camelCase to kebab-case
3636
const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
3737
return ` ${cssKey}: ${value};`;
38-
})
39-
.join("\n");
38+
});
39+
40+
// Add background: transparent to pre/code elements to prevent double backgrounds
41+
if (selector.startsWith("pre") || selector.startsWith("code")) {
42+
entries.push(" background: transparent;");
43+
}
44+
45+
return entries.join("\n");
4046
}
4147

4248
// Generate CSS content
@@ -55,7 +61,7 @@ function generateCSS(): string {
5561
];
5662

5763
for (const [selector, props] of Object.entries(syntaxStyleNoBackgrounds)) {
58-
const cssRules = cssPropertiesToString(props);
64+
const cssRules = cssPropertiesToString(props, selector);
5965
if (cssRules.trim().length > 0) {
6066
// Handle selectors that need .token prefix
6167
let cssSelector = selector;

src/components/Messages/AssistantMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const RawContent = styled.pre`
2121
word-break: break-word;
2222
margin: 0;
2323
padding: 8px;
24-
background: rgba(0, 0, 0, 0.2);
24+
background: var(--color-code-bg);
2525
border-radius: 3px;
2626
`;
2727

src/components/Messages/MarkdownComponents.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const markdownComponents = {
3838
padding: "0.25em 0.5em",
3939
border: "1px solid rgba(255, 255, 255, 0.1)",
4040
borderRadius: "4px",
41-
background: "rgba(0, 0, 0, 0.2)",
41+
background: "var(--color-code-bg)",
4242
}}
4343
>
4444
{children}

src/components/Messages/Mermaid.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ export const Mermaid: React.FC<{ chart: string }> = ({ chart }) => {
165165
<div
166166
style={{
167167
color: "var(--color-text-secondary)",
168-
background: "rgba(0, 0, 0, 0.2)",
168+
background: "var(--color-code-bg)",
169169
padding: "12px",
170170
fontStyle: "italic",
171171
}}
@@ -189,7 +189,7 @@ export const Mermaid: React.FC<{ chart: string }> = ({ chart }) => {
189189
style={{
190190
position: "relative",
191191
margin: "1em 0",
192-
background: "rgba(0, 0, 0, 0.2)",
192+
background: "var(--color-code-bg)",
193193
borderRadius: "4px",
194194
padding: "16px",
195195
}}
@@ -243,7 +243,7 @@ export const Mermaid: React.FC<{ chart: string }> = ({ chart }) => {
243243
ref={modalContainerRef}
244244
className="mermaid-container mermaid-modal"
245245
style={{
246-
background: "rgba(0, 0, 0, 0.2)",
246+
background: "var(--color-code-bg)",
247247
padding: "24px",
248248
borderRadius: "8px",
249249
minWidth: "80vw",

src/components/RightSidebar/CodeReview/FileTree.tsx

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
* FileTree - Displays file hierarchy with diff statistics
33
*/
44

5-
import React, { useState } from "react";
5+
import React from "react";
66
import styled from "@emotion/styled";
77
import type { FileTreeNode } from "@/utils/git/numstatParser";
8+
import { usePersistedState } from "@/hooks/usePersistedState";
9+
import { getFileTreeExpandStateKey } from "@/constants/storage";
810

911
const TreeContainer = styled.div`
1012
flex: 1;
@@ -194,8 +196,30 @@ const TreeNodeContent: React.FC<{
194196
onSelectFile: (path: string | null) => void;
195197
commonPrefix: string | null;
196198
getFileReadStatus?: (filePath: string) => { total: number; read: number } | null;
197-
}> = ({ node, depth, selectedPath, onSelectFile, commonPrefix, getFileReadStatus }) => {
198-
const [isOpen, setIsOpen] = useState(depth < 2); // Auto-expand first 2 levels
199+
expandStateMap: Record<string, boolean>;
200+
setExpandStateMap: (
201+
value: Record<string, boolean> | ((prev: Record<string, boolean>) => Record<string, boolean>)
202+
) => void;
203+
}> = ({
204+
node,
205+
depth,
206+
selectedPath,
207+
onSelectFile,
208+
commonPrefix,
209+
getFileReadStatus,
210+
expandStateMap,
211+
setExpandStateMap,
212+
}) => {
213+
// Check if user has manually set expand state for this directory
214+
const hasManualState = node.path in expandStateMap;
215+
const isOpen = hasManualState ? expandStateMap[node.path] : depth < 2; // Default: auto-expand first 2 levels
216+
217+
const setIsOpen = (open: boolean) => {
218+
setExpandStateMap((prev) => ({
219+
...prev,
220+
[node.path]: open,
221+
}));
222+
};
199223

200224
const handleClick = (e: React.MouseEvent) => {
201225
if (node.isDirectory) {
@@ -295,6 +319,8 @@ const TreeNodeContent: React.FC<{
295319
onSelectFile={onSelectFile}
296320
commonPrefix={commonPrefix}
297321
getFileReadStatus={getFileReadStatus}
322+
expandStateMap={expandStateMap}
323+
setExpandStateMap={setExpandStateMap}
298324
/>
299325
))}
300326
</>
@@ -308,6 +334,7 @@ interface FileTreeExternalProps {
308334
isLoading?: boolean;
309335
commonPrefix?: string | null;
310336
getFileReadStatus?: (filePath: string) => { total: number; read: number } | null;
337+
workspaceId: string;
311338
}
312339

313340
export const FileTree: React.FC<FileTreeExternalProps> = ({
@@ -317,7 +344,15 @@ export const FileTree: React.FC<FileTreeExternalProps> = ({
317344
isLoading = false,
318345
commonPrefix = null,
319346
getFileReadStatus,
347+
workspaceId,
320348
}) => {
349+
// Use persisted state for expand/collapse per workspace (lifted to parent to avoid O(n) re-renders)
350+
const [expandStateMap, setExpandStateMap] = usePersistedState<Record<string, boolean>>(
351+
getFileTreeExpandStateKey(workspaceId),
352+
{},
353+
{ listener: true }
354+
);
355+
321356
// Find the node at the common prefix path to start rendering from
322357
const startNode = React.useMemo(() => {
323358
if (!commonPrefix || !root) return root;
@@ -355,6 +390,8 @@ export const FileTree: React.FC<FileTreeExternalProps> = ({
355390
onSelectFile={onSelectFile}
356391
commonPrefix={commonPrefix}
357392
getFileReadStatus={getFileReadStatus}
393+
expandStateMap={expandStateMap}
394+
setExpandStateMap={setExpandStateMap}
358395
/>
359396
))
360397
) : (

src/components/RightSidebar/CodeReview/HunkViewer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ const HunkContent = styled.div`
107107
font-size: 11px;
108108
line-height: 1.4;
109109
overflow-x: auto;
110-
background: rgba(0, 0, 0, 0.2);
110+
background: var(--color-code-bg);
111111
112112
/* CSS Grid ensures all diff lines span the same width (width of longest line) */
113113
display: grid;

src/components/RightSidebar/CodeReview/ReviewPanel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,7 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
735735
isLoading={isLoadingTree}
736736
commonPrefix={commonPrefix}
737737
getFileReadStatus={getFileReadStatus}
738+
workspaceId={workspaceId}
738739
/>
739740
</FileTreeSection>
740741
)}

src/components/shared/DiffRenderer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export const DiffIndicator = styled.span<{ type: DiffLineType }>`
111111
export const DiffContainer = styled.div<{ fontSize?: string; maxHeight?: string }>`
112112
margin: 0;
113113
padding: 6px 0;
114-
background: rgba(0, 0, 0, 0.2);
114+
background: var(--color-code-bg);
115115
border-radius: 3px;
116116
font-size: ${({ fontSize }) => fontSize ?? "12px"};
117117
line-height: 1.4;

src/components/tools/BashToolCall.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const ScriptPreview = styled.span`
3030
const OutputBlock = styled.pre`
3131
margin: 0;
3232
padding: 6px 8px;
33-
background: rgba(0, 0, 0, 0.2);
33+
background: var(--color-code-bg);
3434
border-radius: 3px;
3535
border-left: 2px solid #4caf50;
3636
font-size: 11px;

src/components/tools/FileReadToolCall.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const MetadataText = styled.span`
3535
const ContentBlock = styled.div`
3636
margin: 0;
3737
padding: 6px 8px;
38-
background: rgba(0, 0, 0, 0.2);
38+
background: var(--color-code-bg);
3939
border-radius: 3px;
4040
font-size: 11px;
4141
line-height: 1.4;
@@ -79,7 +79,7 @@ const FileInfoRow = styled.div`
7979
flex-wrap: wrap;
8080
gap: 16px;
8181
padding: 6px 8px;
82-
background: rgba(0, 0, 0, 0.2);
82+
background: var(--color-code-bg);
8383
border-radius: 3px;
8484
font-size: 11px;
8585
line-height: 1.4;

0 commit comments

Comments
 (0)