Skip to content

Commit 4121ed5

Browse files
committed
fix: keep chat workspace files rail collapsed
1 parent 7d3f196 commit 4121ed5

4 files changed

Lines changed: 138 additions & 23 deletions

File tree

ui/src/styles/chat/sidebar.css

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
/* Split View Layout */
22
.chat-workbench {
33
display: grid;
4-
grid-template-columns: minmax(0, 1fr) minmax(230px, 280px);
4+
grid-template-columns: minmax(0, 1fr);
55
gap: 0;
66
flex: 1;
77
min-height: 0;
88
overflow: hidden;
99
}
1010

11+
.chat-workbench--workspace-rail-open {
12+
grid-template-columns: minmax(0, 1fr) minmax(230px, 280px);
13+
}
14+
1115
.chat-split-container {
1216
display: flex;
1317
gap: 0;
@@ -75,14 +79,22 @@
7579
line-height: 1.2;
7680
}
7781

78-
.chat-workspace-rail__refresh {
82+
.chat-workspace-rail__actions {
83+
display: inline-flex;
84+
align-items: center;
85+
gap: 4px;
86+
}
87+
88+
.chat-workspace-rail__refresh,
89+
.chat-workspace-rail__close {
7990
width: 32px;
8091
min-width: 32px;
8192
height: 32px;
8293
padding: 0;
8394
}
8495

8596
.chat-workspace-rail__refresh svg,
97+
.chat-workspace-rail__close svg,
8698
.chat-workspace-rail__file-icon svg {
8799
width: 15px;
88100
height: 15px;

ui/src/ui/app-render.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ type ChatWorkspaceFilesState = {
681681
error: string | null;
682682
list: AgentsFilesListResult | null;
683683
loading: boolean;
684+
open: boolean;
684685
requestId: number;
685686
};
686687

@@ -701,6 +702,7 @@ function getChatWorkspaceFilesState(state: AppViewState, agentId: string): ChatW
701702
error: null,
702703
list: null,
703704
loading: false,
705+
open: false,
704706
requestId: 0,
705707
};
706708
chatWorkspaceFilesStates.set(state, next);
@@ -2096,6 +2098,7 @@ export function renderApp(state: AppViewState) {
20962098
};
20972099
if (
20982100
isChat &&
2101+
chatWorkspaceFiles.open &&
20992102
state.connected &&
21002103
state.agentsList &&
21012104
!chatWorkspaceFiles.loading &&
@@ -2104,7 +2107,19 @@ export function renderApp(state: AppViewState) {
21042107
) {
21052108
loadChatWorkspaceFiles();
21062109
}
2110+
const toggleChatWorkspaceFiles = () => {
2111+
chatWorkspaceFiles.open = !chatWorkspaceFiles.open;
2112+
if (chatWorkspaceFiles.open && chatWorkspaceFiles.list?.agentId !== chatAgentId) {
2113+
loadChatWorkspaceFiles();
2114+
}
2115+
requestHostUpdate?.();
2116+
};
2117+
const closeChatWorkspaceFiles = () => {
2118+
chatWorkspaceFiles.open = false;
2119+
requestHostUpdate?.();
2120+
};
21072121
const refreshChatWorkspaceFiles = () => {
2122+
chatWorkspaceFiles.open = true;
21082123
loadChatWorkspaceFiles({ force: true });
21092124
};
21102125
function loadChatWorkspaceFiles(opts?: { force?: boolean }) {
@@ -3524,16 +3539,19 @@ export function renderApp(state: AppViewState) {
35243539
sessions: state.sessionsResult,
35253540
composerControls: renderGuardedChatControls(state),
35263541
workspaceFiles: {
3542+
activeName: chatWorkspaceFiles.activeName,
35273543
agentId: chatAgentId,
3544+
error: chatWorkspaceFiles.error,
35283545
list:
35293546
chatWorkspaceFiles.list?.agentId === chatAgentId
35303547
? chatWorkspaceFiles.list
35313548
: null,
35323549
loading: chatWorkspaceFiles.loading,
3533-
error: chatWorkspaceFiles.error,
3534-
activeName: chatWorkspaceFiles.activeName,
3535-
onRefresh: refreshChatWorkspaceFiles,
3550+
open: chatWorkspaceFiles.open,
3551+
onClose: closeChatWorkspaceFiles,
35363552
onOpenFile: openChatWorkspaceFile,
3553+
onRefresh: refreshChatWorkspaceFiles,
3554+
onToggle: toggleChatWorkspaceFiles,
35373555
},
35383556
autoExpandToolCalls: false,
35393557
onRefresh: () => {

ui/src/ui/views/chat.test.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -865,13 +865,51 @@ describe("chat goal status", () => {
865865
});
866866

867867
describe("chat composer workbench", () => {
868-
it("renders session controls in the composer and workspace files in the rail", () => {
868+
it("keeps workspace files hidden until explicitly opened", () => {
869+
const onToggle = vi.fn();
870+
const container = renderChatView({
871+
workspaceFiles: {
872+
activeName: null,
873+
agentId: "main",
874+
error: null,
875+
list: {
876+
agentId: "main",
877+
workspace: "/workspace",
878+
files: [{ name: "AGENTS.md", path: "/workspace/AGENTS.md", missing: false }],
879+
},
880+
loading: false,
881+
open: false,
882+
onClose: () => undefined,
883+
onOpenFile: () => undefined,
884+
onRefresh: () => undefined,
885+
onToggle,
886+
},
887+
});
888+
889+
const toggle = container.querySelector<HTMLButtonElement>(
890+
'.agent-chat__input-btn[aria-label="Workspace files"]',
891+
);
892+
expect(toggle?.getAttribute("aria-expanded")).toBe("false");
893+
expect(container.querySelector(".chat-workspace-rail")).toBeNull();
894+
expect(container.querySelector(".chat-workbench")?.className).not.toContain(
895+
"chat-workbench--workspace-rail-open",
896+
);
897+
898+
toggle?.click();
899+
900+
expect(onToggle).toHaveBeenCalledTimes(1);
901+
});
902+
903+
it("renders session controls in the composer and workspace files in the opened rail", () => {
904+
const onClose = vi.fn();
869905
const onRefresh = vi.fn();
870906
const onOpenFile = vi.fn();
871907
const container = renderChatView({
872908
composerControls: html`<button class="test-composer-control">Model</button>`,
873909
workspaceFiles: {
910+
activeName: "AGENTS.md",
874911
agentId: "main",
912+
error: null,
875913
list: {
876914
agentId: "main",
877915
workspace: "/workspace",
@@ -885,16 +923,25 @@ describe("chat composer workbench", () => {
885923
],
886924
},
887925
loading: false,
888-
error: null,
889-
activeName: "AGENTS.md",
926+
open: true,
927+
onClose,
890928
onRefresh,
891929
onOpenFile,
930+
onToggle: () => undefined,
892931
},
893932
});
894933

895934
expect(
896935
container.querySelector(".agent-chat__composer-controls .test-composer-control"),
897936
).not.toBeNull();
937+
expect(
938+
container
939+
.querySelector('.agent-chat__input-btn[aria-label="Workspace files"]')
940+
?.getAttribute("aria-expanded"),
941+
).toBe("true");
942+
expect(container.querySelector(".chat-workbench")?.className).toContain(
943+
"chat-workbench--workspace-rail-open",
944+
);
898945
expect(container.querySelector(".chat-workspace-rail__path")?.textContent?.trim()).toBe(
899946
"/workspace",
900947
);
@@ -903,8 +950,10 @@ describe("chat composer workbench", () => {
903950
expect(file?.textContent).toContain("2 KB");
904951

905952
file?.click();
953+
container.querySelector<HTMLButtonElement>(".chat-workspace-rail__close")?.click();
906954

907955
expect(onOpenFile).toHaveBeenCalledWith("AGENTS.md");
956+
expect(onClose).toHaveBeenCalledTimes(1);
908957
});
909958

910959
it("keeps the secondary New session and Export controls suppressed in the composer", () => {

ui/src/ui/views/chat.ts

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,16 @@ export type ChatProps = {
184184
basePath?: string;
185185
composerControls?: TemplateResult | typeof nothing | ReturnType<typeof guard>;
186186
workspaceFiles?: {
187+
activeName: string | null;
187188
agentId: string;
189+
error: string | null;
188190
list: AgentsFilesListResult | null;
189191
loading: boolean;
190-
error: string | null;
191-
activeName: string | null;
192-
onRefresh: () => void;
192+
open: boolean;
193+
onClose: () => void;
193194
onOpenFile: (name: string) => void;
195+
onRefresh: () => void;
196+
onToggle: () => void;
194197
};
195198
};
196199

@@ -1008,7 +1011,7 @@ function formatWorkspaceFileSize(file: AgentFileEntry): string {
10081011
function renderWorkspaceFileRail(
10091012
workspaceFiles: NonNullable<ChatProps["workspaceFiles"]> | undefined,
10101013
): TemplateResult | typeof nothing {
1011-
if (!workspaceFiles) {
1014+
if (!workspaceFiles?.open) {
10121015
return nothing;
10131016
}
10141017
const files = workspaceFiles.list?.files ?? [];
@@ -1019,16 +1022,27 @@ function renderWorkspaceFileRail(
10191022
<span class="chat-workspace-rail__eyebrow">Workspace</span>
10201023
<strong>Files</strong>
10211024
</div>
1022-
<button
1023-
class="btn btn--ghost btn--sm chat-workspace-rail__refresh"
1024-
type="button"
1025-
title="Refresh files"
1026-
aria-label="Refresh files"
1027-
?disabled=${workspaceFiles.loading}
1028-
@click=${workspaceFiles.onRefresh}
1029-
>
1030-
${icons.refresh}
1031-
</button>
1025+
<div class="chat-workspace-rail__actions">
1026+
<button
1027+
class="btn btn--ghost btn--sm chat-workspace-rail__refresh"
1028+
type="button"
1029+
title="Refresh files"
1030+
aria-label="Refresh files"
1031+
?disabled=${workspaceFiles.loading}
1032+
@click=${workspaceFiles.onRefresh}
1033+
>
1034+
${icons.refresh}
1035+
</button>
1036+
<button
1037+
class="btn btn--ghost btn--sm chat-workspace-rail__close"
1038+
type="button"
1039+
title="Hide workspace files"
1040+
aria-label="Hide workspace files"
1041+
@click=${workspaceFiles.onClose}
1042+
>
1043+
${icons.x}
1044+
</button>
1045+
</div>
10321046
</div>
10331047
${workspaceFiles.list?.workspace
10341048
? html`<div class="chat-workspace-rail__path" title=${workspaceFiles.list.workspace}>
@@ -1997,7 +2011,11 @@ export function renderChat(props: ChatProps) {
19972011
: nothing}
19982012
${renderSearchBar(requestUpdate)} ${renderPinnedSection(props, pinned, requestUpdate)}
19992013
2000-
<div class="chat-workbench">
2014+
<div
2015+
class="chat-workbench ${props.workspaceFiles?.open
2016+
? "chat-workbench--workspace-rail-open"
2017+
: ""}"
2018+
>
20012019
<div class="chat-split-container ${sidebarOpen ? "chat-split-container--open" : ""}">
20022020
<div
20032021
class="chat-main"
@@ -2142,6 +2160,24 @@ export function renderChat(props: ChatProps) {
21422160
<span class="agent-chat__control-label">${t("chat.composer.attachFile")}</span>
21432161
</button>
21442162
2163+
${props.workspaceFiles
2164+
? html`
2165+
<button
2166+
type="button"
2167+
class="agent-chat__input-btn ${props.workspaceFiles.open
2168+
? "agent-chat__input-btn--active"
2169+
: ""}"
2170+
@click=${props.workspaceFiles.onToggle}
2171+
title="Workspace files"
2172+
aria-label="Workspace files"
2173+
aria-expanded=${props.workspaceFiles.open ? "true" : "false"}
2174+
?disabled=${!props.connected}
2175+
>
2176+
${icons.fileText}
2177+
<span class="agent-chat__control-label">Workspace files</span>
2178+
</button>
2179+
`
2180+
: nothing}
21452181
${props.onToggleRealtimeTalk
21462182
? html`
21472183
<button

0 commit comments

Comments
 (0)