From c26359917f8ffa449d20e7e2eab773dfea2f9c97 Mon Sep 17 00:00:00 2001 From: spellitwithaph Date: Wed, 27 May 2026 10:10:17 -0400 Subject: [PATCH 1/8] feat: add collapse/expand toggle for workspace panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a chevron-right collapse button after the last tab (Files, Git, Terminal, Info) in the workspace panel header. Clicking it hides the workspace panel and lets the main chat column fill the full width. When collapsed, an expand strip with a chevron-left button appears below the context bar to re-open the workspace panel. Changes: - Add workspacePanelCollapsed boolean to AppState - Add collapse button to WorkspacePanel header tabs - Add expand strip with toggle button in main column when collapsed - Adjust grid layout to hide panel and stretch main when collapsed - Handle collapsed state at ≤1180px responsive breakpoint --- src/client/src/appState.ts | 2 ++ src/client/src/components/PiWebApp.ts | 9 +++++++-- src/client/src/components/WorkspacePanel.ts | 7 +++++++ src/client/src/components/shared.ts | 11 ++++++++++- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/client/src/appState.ts b/src/client/src/appState.ts index 409bf73..17776d7 100644 --- a/src/client/src/appState.ts +++ b/src/client/src/appState.ts @@ -45,6 +45,7 @@ export interface AppState { activeTerminalCount: number; selectedTerminalId: string | undefined; piWebStatus: PiWebStatusResponse | undefined; + workspacePanelCollapsed: boolean; error: string; } @@ -133,6 +134,7 @@ export function initialAppState(): AppState { activeTerminalCount: 0, selectedTerminalId: undefined, piWebStatus: undefined, + workspacePanelCollapsed: false, error: "", }; } diff --git a/src/client/src/components/PiWebApp.ts b/src/client/src/components/PiWebApp.ts index 564f86c..fd4a1fa 100644 --- a/src/client/src/components/PiWebApp.ts +++ b/src/client/src/components/PiWebApp.ts @@ -511,7 +511,11 @@ export class PiWebApp extends LitElement { const panelContext = workspace === undefined ? undefined : this.createWorkspacePanelContext(workspace); const workspaceLabelItems = workspace === undefined ? [] : this.plugins.getWorkspaceLabelItems(this.state, workspace); const emptyState = workspace === undefined ? this.workspacePanelEmptyState() : undefined; - return html` { this.openWorkspaceTool(tool); }}>`; + return html` { this.openWorkspaceTool(tool); }} .onToggleCollapse=${() => { this.toggleWorkspacePanelCollapse(); }}>`; + } + + private toggleWorkspacePanelCollapse(): void { + this.setState({ workspacePanelCollapsed: !this.state.workspacePanelCollapsed }); } private renderNavigationPanel(autoSwitchToChat: boolean) { @@ -1192,10 +1196,11 @@ export class PiWebApp extends LitElement { override render() { const state = this.state; return html` -
+
${this.renderContextBar()} + ${state.workspacePanelCollapsed ? html`
` : null}
diff --git a/src/client/src/components/WorkspacePanel.ts b/src/client/src/components/WorkspacePanel.ts index 826b293..11d5e75 100644 --- a/src/client/src/components/WorkspacePanel.ts +++ b/src/client/src/components/WorkspacePanel.ts @@ -19,7 +19,9 @@ export class WorkspacePanel extends LitElement { @property({ attribute: false }) panels: QualifiedWorkspacePanelContribution[] = []; @property({ attribute: false }) workspaceLabelItems: WorkspaceLabelItem[] = []; @property({ type: Boolean }) hideToolTabs = false; + @property({ type: Boolean }) collapsed = false; @property({ attribute: false }) onSelectTool: (tool: QualifiedContributionId) => void = () => undefined; + @property({ attribute: false }) onToggleCollapse: () => void = () => undefined; @query(".workspace-header-strip") private workspaceHeaderStrip?: HTMLElement | null; @state() private workspaceHeaderCanScrollLeft = false; @state() private workspaceHeaderCanScrollRight = false; @@ -69,6 +71,7 @@ export class WorkspacePanel extends LitElement { ${visiblePanels.map((panel) => html` `)} +
`} ${renderWorkspaceLabel(workspace.label, this.workspaceLabelItems, workspace.path)} @@ -92,6 +95,10 @@ export class WorkspacePanel extends LitElement { return html`${panel.title} ${badge}`; } + private renderCollapseIcon(): TemplateResult { + return html``; + } + private renderEmptyState(state: WorkspacePanelEmptyState): TemplateResult { return html`
diff --git a/src/client/src/components/shared.ts b/src/client/src/components/shared.ts index 41e06be..118a9b8 100644 --- a/src/client/src/components/shared.ts +++ b/src/client/src/components/shared.ts @@ -52,6 +52,8 @@ export interface CompletionItem { export const appStyles = css` :host { display: block; height: 100dvh; box-sizing: border-box; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); color: var(--pi-text); background: var(--pi-bg); font: 14px system-ui, sans-serif; } .shell { display: grid; grid-template-columns: 340px minmax(420px, 1fr) minmax(360px, 42vw); height: 100%; min-height: 0; } + .shell.workspace-panel-collapsed { grid-template-columns: 340px minmax(420px, 1fr); } + .shell.workspace-panel-collapsed > workspace-panel { display: none; } aside { display: flex; flex-direction: column; min-height: 0; border-right: 1px solid var(--pi-border); overflow: hidden; } header { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px; border-bottom: 1px solid var(--pi-border); } .header-actions { display: flex; align-items: center; gap: 8px; } @@ -93,6 +95,9 @@ export const appStyles = css` .mobile-navigation-tab, .mobile-navigation-panel { display: none; } .mobile-tabs button.selected { border-color: var(--pi-accent); background: var(--pi-selection-bg); } .tab-badge { display: inline-block; min-width: 14px; margin-left: 4px; border: 1px solid var(--pi-success-border); border-radius: 999px; background: var(--pi-success-surface); color: var(--pi-success); padding: 0 5px; font-size: 11px; line-height: 16px; text-align: center; } + .expand-panel-strip { flex: 0 0 auto; display: flex; align-items: center; padding: 4px 8px; border-bottom: 1px solid var(--pi-border-muted); } + .expand-workspace-panel-button { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; padding: 0; border-radius: 6px; } + .expand-workspace-panel-button .expand-icon { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; } workspace-panel { min-width: 0; min-height: 0; border-left: 1px solid var(--pi-border); overflow: hidden; } @media (max-width: 1180px) { .shell { grid-template-columns: 340px minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); } @@ -102,6 +107,8 @@ export const appStyles = css` .shell.workspace-view main { grid-row: 1; min-height: auto; } .shell.workspace-view > workspace-panel { grid-column: 2; grid-row: 2; display: flex; border-left: 0; } .shell:not(.workspace-view) > workspace-panel { display: none; } + .shell.workspace-panel-collapsed > workspace-panel { display: none; } + .shell.workspace-panel-collapsed .expand-panel-strip { display: flex; } main.workspace-view chat-view, main.workspace-view prompt-editor, main.workspace-view status-bar, main.workspace-view .empty { display: none; } main.workspace-view { overflow: hidden; } @@ -140,8 +147,10 @@ export const workspacePanelStyles = css` .workspace-header-scroll-frame::after { right: 0; background: linear-gradient(270deg, color-mix(in srgb, var(--pi-shadow-strong) 55%, transparent) 0%, transparent 100%); } .workspace-header-scroll-frame.can-scroll-left::before, .workspace-header-scroll-frame.can-scroll-right::after { opacity: 1; } .workspace-header-strip { display: flex; justify-content: space-between; align-items: center; gap: 8px; min-width: 0; padding: 8px; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scrollbar-width: thin; } - .tabs { flex: 0 0 auto; display: flex; gap: 6px; } + .tabs { flex: 0 0 auto; display: flex; gap: 6px; align-items: center; } .tabs button { flex: 0 0 auto; white-space: nowrap; } + .collapse-button { display: inline-flex; align-items: center; justify-content: center; width: auto; height: auto; padding: 5px 7px; } + .collapse-icon { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; } button { display: inline-flex; align-items: center; gap: 5px; border: 1px solid var(--pi-border); border-radius: 7px; background: var(--pi-surface); color: var(--pi-text); padding: 5px 7px; cursor: pointer; } button.selected { border-color: var(--pi-accent); background: var(--pi-selection-bg); } .tab-badge { display: inline-block; min-width: 14px; border: 1px solid var(--pi-success-border); border-radius: 999px; background: var(--pi-success-surface); color: var(--pi-success); padding: 0 5px; font-size: 11px; line-height: 16px; text-align: center; } From c8b05427fd0d3e58eec224d66659e6a492c28932 Mon Sep 17 00:00:00 2001 From: spellitwithaph Date: Wed, 27 May 2026 10:19:59 -0400 Subject: [PATCH 2/8] fix: tooltip reads 'Toggle Panel' and expand button aligns right - Change title/aria-label on both collapse and expand buttons to 'Toggle Panel' - Right-align the expand-panel-strip so the toggle icon sits on the right edge of the main column when the workspace panel is collapsed --- src/client/src/components/PiWebApp.ts | 2 +- src/client/src/components/WorkspacePanel.ts | 2 +- src/client/src/components/shared.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/src/components/PiWebApp.ts b/src/client/src/components/PiWebApp.ts index fd4a1fa..e6475fd 100644 --- a/src/client/src/components/PiWebApp.ts +++ b/src/client/src/components/PiWebApp.ts @@ -1200,7 +1200,7 @@ export class PiWebApp extends LitElement {
${this.renderContextBar()} - ${state.workspacePanelCollapsed ? html`
` : null} + ${state.workspacePanelCollapsed ? html`
` : null}
diff --git a/src/client/src/components/WorkspacePanel.ts b/src/client/src/components/WorkspacePanel.ts index 11d5e75..290f9d5 100644 --- a/src/client/src/components/WorkspacePanel.ts +++ b/src/client/src/components/WorkspacePanel.ts @@ -71,7 +71,7 @@ export class WorkspacePanel extends LitElement { ${visiblePanels.map((panel) => html` `)} - +
`} ${renderWorkspaceLabel(workspace.label, this.workspaceLabelItems, workspace.path)} diff --git a/src/client/src/components/shared.ts b/src/client/src/components/shared.ts index 118a9b8..ce5f08f 100644 --- a/src/client/src/components/shared.ts +++ b/src/client/src/components/shared.ts @@ -95,7 +95,7 @@ export const appStyles = css` .mobile-navigation-tab, .mobile-navigation-panel { display: none; } .mobile-tabs button.selected { border-color: var(--pi-accent); background: var(--pi-selection-bg); } .tab-badge { display: inline-block; min-width: 14px; margin-left: 4px; border: 1px solid var(--pi-success-border); border-radius: 999px; background: var(--pi-success-surface); color: var(--pi-success); padding: 0 5px; font-size: 11px; line-height: 16px; text-align: center; } - .expand-panel-strip { flex: 0 0 auto; display: flex; align-items: center; padding: 4px 8px; border-bottom: 1px solid var(--pi-border-muted); } + .expand-panel-strip { flex: 0 0 auto; display: flex; align-items: center; justify-content: flex-end; padding: 4px 8px; border-bottom: 1px solid var(--pi-border-muted); } .expand-workspace-panel-button { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; padding: 0; border-radius: 6px; } .expand-workspace-panel-button .expand-icon { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; } workspace-panel { min-width: 0; min-height: 0; border-left: 1px solid var(--pi-border); overflow: hidden; } From 1784b1e6c6f0284321b69b62c34828d57c99853a Mon Sep 17 00:00:00 2001 From: spellitwithaph Date: Wed, 27 May 2026 10:32:32 -0400 Subject: [PATCH 3/8] fix: add responsive collapsed overrides for tablet/mobile breakpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The desktop .shell.workspace-panel-collapsed selector had higher specificity than the media-query .shell rules, causing horizontal overflow at ≤1180px and ≤760px widths. Add matching collapsed grid overrides inside each breakpoint. --- src/client/src/components/shared.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/src/components/shared.ts b/src/client/src/components/shared.ts index ce5f08f..93cdd92 100644 --- a/src/client/src/components/shared.ts +++ b/src/client/src/components/shared.ts @@ -101,6 +101,7 @@ export const appStyles = css` workspace-panel { min-width: 0; min-height: 0; border-left: 1px solid var(--pi-border); overflow: hidden; } @media (max-width: 1180px) { .shell { grid-template-columns: 340px minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); } + .shell.workspace-panel-collapsed { grid-template-columns: 340px minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); } aside { grid-row: 1 / 3; } main { grid-column: 2; grid-row: 1 / 3; } .mobile-tabs-frame { display: flex; } @@ -115,6 +116,7 @@ export const appStyles = css` } @media (max-width: 760px) { .shell { grid-template-columns: minmax(0, 1fr); } + .shell.workspace-panel-collapsed { grid-template-columns: minmax(0, 1fr); } aside { display: none; } main, .shell.workspace-view > workspace-panel { grid-column: 1; } .context-bar { display: flex; } From 6d45505afc0e4c9a86089e4a47644cb6c5fc653b Mon Sep 17 00:00:00 2001 From: spellitwithaph Date: Wed, 27 May 2026 10:36:31 -0400 Subject: [PATCH 4/8] fix: hide panel toggle icon at responsive breakpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workspace panel is already hidden at ≤1180px by responsive CSS, so the expand/collapse toggle icon is meaningless there. Hide the expand-panel-strip inside both media queries. --- src/client/src/components/shared.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/src/components/shared.ts b/src/client/src/components/shared.ts index 93cdd92..7b282cb 100644 --- a/src/client/src/components/shared.ts +++ b/src/client/src/components/shared.ts @@ -109,7 +109,7 @@ export const appStyles = css` .shell.workspace-view > workspace-panel { grid-column: 2; grid-row: 2; display: flex; border-left: 0; } .shell:not(.workspace-view) > workspace-panel { display: none; } .shell.workspace-panel-collapsed > workspace-panel { display: none; } - .shell.workspace-panel-collapsed .expand-panel-strip { display: flex; } + .expand-panel-strip { display: none; } main.workspace-view chat-view, main.workspace-view prompt-editor, main.workspace-view status-bar, main.workspace-view .empty { display: none; } main.workspace-view { overflow: hidden; } From 2e2ca283cf8cc9f1f9d6060dc5e947d6c72ef8e4 Mon Sep 17 00:00:00 2001 From: spellitwithaph Date: Wed, 27 May 2026 10:38:42 -0400 Subject: [PATCH 5/8] fix: remove unused collapsed property from WorkspacePanel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The collapsed state is owned by PiWebApp and used only to toggle the shell CSS class and hide the element — the WorkspacePanel never reads it. Remove the dead property and its pass-through. --- src/client/src/components/PiWebApp.ts | 2 +- src/client/src/components/WorkspacePanel.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/src/components/PiWebApp.ts b/src/client/src/components/PiWebApp.ts index e6475fd..45980af 100644 --- a/src/client/src/components/PiWebApp.ts +++ b/src/client/src/components/PiWebApp.ts @@ -511,7 +511,7 @@ export class PiWebApp extends LitElement { const panelContext = workspace === undefined ? undefined : this.createWorkspacePanelContext(workspace); const workspaceLabelItems = workspace === undefined ? [] : this.plugins.getWorkspaceLabelItems(this.state, workspace); const emptyState = workspace === undefined ? this.workspacePanelEmptyState() : undefined; - return html` { this.openWorkspaceTool(tool); }} .onToggleCollapse=${() => { this.toggleWorkspacePanelCollapse(); }}>`; + return html` { this.openWorkspaceTool(tool); }} .onToggleCollapse=${() => { this.toggleWorkspacePanelCollapse(); }}>`; } private toggleWorkspacePanelCollapse(): void { diff --git a/src/client/src/components/WorkspacePanel.ts b/src/client/src/components/WorkspacePanel.ts index 290f9d5..0cbdfd8 100644 --- a/src/client/src/components/WorkspacePanel.ts +++ b/src/client/src/components/WorkspacePanel.ts @@ -19,7 +19,6 @@ export class WorkspacePanel extends LitElement { @property({ attribute: false }) panels: QualifiedWorkspacePanelContribution[] = []; @property({ attribute: false }) workspaceLabelItems: WorkspaceLabelItem[] = []; @property({ type: Boolean }) hideToolTabs = false; - @property({ type: Boolean }) collapsed = false; @property({ attribute: false }) onSelectTool: (tool: QualifiedContributionId) => void = () => undefined; @property({ attribute: false }) onToggleCollapse: () => void = () => undefined; @query(".workspace-header-strip") private workspaceHeaderStrip?: HTMLElement | null; From 6abc41b4dce3780855d1f50482771a480a0d2e92 Mon Sep 17 00:00:00 2001 From: spellitwithaph Date: Wed, 27 May 2026 10:41:46 -0400 Subject: [PATCH 6/8] refactor: extract render methods for workspace panel templates Break the dense one-liner workspace-panel and expand-button templates into multi-line formatted render methods for readability and safer future edits. --- src/client/src/components/PiWebApp.ts | 28 +++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/client/src/components/PiWebApp.ts b/src/client/src/components/PiWebApp.ts index 45980af..7db89b6 100644 --- a/src/client/src/components/PiWebApp.ts +++ b/src/client/src/components/PiWebApp.ts @@ -511,13 +511,37 @@ export class PiWebApp extends LitElement { const panelContext = workspace === undefined ? undefined : this.createWorkspacePanelContext(workspace); const workspaceLabelItems = workspace === undefined ? [] : this.plugins.getWorkspaceLabelItems(this.state, workspace); const emptyState = workspace === undefined ? this.workspacePanelEmptyState() : undefined; - return html` { this.openWorkspaceTool(tool); }} .onToggleCollapse=${() => { this.toggleWorkspacePanelCollapse(); }}>`; + return html` + { this.openWorkspaceTool(tool); }} + .onToggleCollapse=${() => { this.toggleWorkspacePanelCollapse(); }} + > + `; } private toggleWorkspacePanelCollapse(): void { this.setState({ workspacePanelCollapsed: !this.state.workspacePanelCollapsed }); } + private renderExpandWorkspacePanelButton() { + return html` +
+ +
+ `; + } + private renderNavigationPanel(autoSwitchToChat: boolean) { const openChatAfter = (action: () => Promise) => this.withChatScrollTransition(async () => { await action(); @@ -1200,7 +1224,7 @@ export class PiWebApp extends LitElement {
${this.renderContextBar()} - ${state.workspacePanelCollapsed ? html`
` : null} + ${state.workspacePanelCollapsed ? this.renderExpandWorkspacePanelButton() : null}
From 559c6f6d1f3dec95a422e1ba18274f34a871083b Mon Sep 17 00:00:00 2001 From: Federico Jaramillo Martinez Date: Thu, 28 May 2026 10:47:16 +0200 Subject: [PATCH 7/8] fix: move workspace panel collapse control to edge --- .changeset/workspace-panel-collapse.md | 5 ++++ src/client/src/appState.ts | 2 -- src/client/src/components/PiWebApp.ts | 31 ++++++++++++++------- src/client/src/components/WorkspacePanel.ts | 6 ---- src/client/src/components/shared.ts | 28 +++++++++---------- 5 files changed, 39 insertions(+), 33 deletions(-) create mode 100644 .changeset/workspace-panel-collapse.md diff --git a/.changeset/workspace-panel-collapse.md b/.changeset/workspace-panel-collapse.md new file mode 100644 index 0000000..42589b4 --- /dev/null +++ b/.changeset/workspace-panel-collapse.md @@ -0,0 +1,5 @@ +--- +"@jmfederico/pi-web": patch +--- + +Add a desktop edge control for collapsing and expanding the workspace tools panel. diff --git a/src/client/src/appState.ts b/src/client/src/appState.ts index 17776d7..409bf73 100644 --- a/src/client/src/appState.ts +++ b/src/client/src/appState.ts @@ -45,7 +45,6 @@ export interface AppState { activeTerminalCount: number; selectedTerminalId: string | undefined; piWebStatus: PiWebStatusResponse | undefined; - workspacePanelCollapsed: boolean; error: string; } @@ -134,7 +133,6 @@ export function initialAppState(): AppState { activeTerminalCount: 0, selectedTerminalId: undefined, piWebStatus: undefined, - workspacePanelCollapsed: false, error: "", }; } diff --git a/src/client/src/components/PiWebApp.ts b/src/client/src/components/PiWebApp.ts index 7db89b6..de145df 100644 --- a/src/client/src/components/PiWebApp.ts +++ b/src/client/src/components/PiWebApp.ts @@ -55,6 +55,7 @@ const REFRESH_LONG_PRESS_MS = 550; @customElement("pi-web-app") export class PiWebApp extends LitElement { @state() private state: AppState = initialAppState(); + @state() private workspacePanelCollapsed = false; @query("chat-view") private chatView?: ChatView; @query("prompt-editor") private promptEditor?: PromptEditor; @query(".context-items") private contextItems?: HTMLElement | null; @@ -513,6 +514,7 @@ export class PiWebApp extends LitElement { const emptyState = workspace === undefined ? this.workspacePanelEmptyState() : undefined; return html` { this.openWorkspaceTool(tool); }} - .onToggleCollapse=${() => { this.toggleWorkspacePanelCollapse(); }} > `; } private toggleWorkspacePanelCollapse(): void { - this.setState({ workspacePanelCollapsed: !this.state.workspacePanelCollapsed }); + this.workspacePanelCollapsed = !this.workspacePanelCollapsed; } - private renderExpandWorkspacePanelButton() { + private renderWorkspacePanelEdgeControl() { + const collapsed = this.workspacePanelCollapsed; + const label = collapsed ? "Expand workspace panel" : "Collapse workspace panel"; return html` -
+
+ >${this.renderWorkspacePanelEdgeIcon(collapsed)}
`; } + private renderWorkspacePanelEdgeIcon(collapsed: boolean) { + const path = collapsed ? "M15 18l-6-6 6-6" : "M9 18l6-6-6-6"; + return html``; + } + private renderNavigationPanel(autoSwitchToChat: boolean) { const openChatAfter = (action: () => Promise) => this.withChatScrollTransition(async () => { await action(); @@ -1220,11 +1231,10 @@ export class PiWebApp extends LitElement { override render() { const state = this.state; return html` -
+
${this.renderContextBar()} - ${state.workspacePanelCollapsed ? this.renderExpandWorkspacePanelButton() : null}
@@ -1246,6 +1256,7 @@ export class PiWebApp extends LitElement { ${state.authDialog !== undefined ? html` { void this.auth.chooseLoginMethod(authType); }} .onSelectProvider=${(providerId: string, authType: "oauth" | "api_key") => { void this.auth.selectLoginProvider(providerId, authType); }} .onApiKeyInput=${(value: string) => { this.auth.updateApiKey(value); }} .onSaveApiKey=${() => { void this.auth.saveApiKey(); }} .onLogoutProvider=${(providerId: string) => { void this.auth.logoutProvider(providerId); }} .onOAuthInput=${(value: string) => { this.auth.updateOAuthInput(value); }} .onOAuthRespond=${(value?: string) => { void this.auth.respondOAuth(value); }} .onOAuthCancel=${() => { void this.auth.cancelOAuth(); }} .onCancel=${() => { this.auth.closeDialog(); }}>` : null} ` : html`
${this.sessionEmptyMessage()}
`}
+ ${this.renderWorkspacePanelEdgeControl()} ${this.renderWorkspacePanel()} ${state.actionPaletteOpen ? html` { this.setState({ actionPaletteOpen: false }); this.runAction(action); }} .onCancel=${() => { this.setState({ actionPaletteOpen: false }); }}>` : null} ${state.projectDialogOpen ? html` this.projects.addProject(path, create)} .onCancel=${() => { this.setState({ projectDialogOpen: false }); }}>` : null} diff --git a/src/client/src/components/WorkspacePanel.ts b/src/client/src/components/WorkspacePanel.ts index 0cbdfd8..826b293 100644 --- a/src/client/src/components/WorkspacePanel.ts +++ b/src/client/src/components/WorkspacePanel.ts @@ -20,7 +20,6 @@ export class WorkspacePanel extends LitElement { @property({ attribute: false }) workspaceLabelItems: WorkspaceLabelItem[] = []; @property({ type: Boolean }) hideToolTabs = false; @property({ attribute: false }) onSelectTool: (tool: QualifiedContributionId) => void = () => undefined; - @property({ attribute: false }) onToggleCollapse: () => void = () => undefined; @query(".workspace-header-strip") private workspaceHeaderStrip?: HTMLElement | null; @state() private workspaceHeaderCanScrollLeft = false; @state() private workspaceHeaderCanScrollRight = false; @@ -70,7 +69,6 @@ export class WorkspacePanel extends LitElement { ${visiblePanels.map((panel) => html` `)} -
`} ${renderWorkspaceLabel(workspace.label, this.workspaceLabelItems, workspace.path)} @@ -94,10 +92,6 @@ export class WorkspacePanel extends LitElement { return html`${panel.title} ${badge}`; } - private renderCollapseIcon(): TemplateResult { - return html``; - } - private renderEmptyState(state: WorkspacePanelEmptyState): TemplateResult { return html`
diff --git a/src/client/src/components/shared.ts b/src/client/src/components/shared.ts index 7b282cb..d752c74 100644 --- a/src/client/src/components/shared.ts +++ b/src/client/src/components/shared.ts @@ -51,15 +51,13 @@ export interface CompletionItem { export const appStyles = css` :host { display: block; height: 100dvh; box-sizing: border-box; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); color: var(--pi-text); background: var(--pi-bg); font: 14px system-ui, sans-serif; } - .shell { display: grid; grid-template-columns: 340px minmax(420px, 1fr) minmax(360px, 42vw); height: 100%; min-height: 0; } - .shell.workspace-panel-collapsed { grid-template-columns: 340px minmax(420px, 1fr); } - .shell.workspace-panel-collapsed > workspace-panel { display: none; } - aside { display: flex; flex-direction: column; min-height: 0; border-right: 1px solid var(--pi-border); overflow: hidden; } + .shell { display: grid; grid-template-columns: 340px minmax(420px, 1fr) 1px minmax(360px, 42vw); height: 100%; min-height: 0; } + aside { grid-column: 1; display: flex; flex-direction: column; min-height: 0; border-right: 1px solid var(--pi-border); overflow: hidden; } header { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px; border-bottom: 1px solid var(--pi-border); } .header-actions { display: flex; align-items: center; gap: 8px; } project-list, workspace-list { flex: 0 0 auto; max-height: 26%; overflow: auto; border-bottom: 1px solid var(--pi-border-muted); } session-list { flex: 1 1 auto; min-height: 0; overflow: auto; } - main { display: flex; flex-direction: column; min-width: 0; min-height: 0; } + main { grid-column: 2; display: flex; flex-direction: column; min-width: 0; min-height: 0; } .context-bar { position: relative; flex: 0 0 auto; min-width: 0; display: none; align-items: center; gap: 0; padding: 6px 0; border-bottom: 1px solid var(--pi-border-muted); background: var(--pi-bg); } .context-bar::before, .context-bar::after { content: ""; position: absolute; top: 0; bottom: 0; z-index: 2; width: 20px; opacity: 0; pointer-events: none; transition: opacity .15s ease; } .context-bar::before { left: 0; background: linear-gradient(90deg, color-mix(in srgb, var(--pi-shadow-strong) 55%, transparent) 0%, transparent 100%); } @@ -95,28 +93,30 @@ export const appStyles = css` .mobile-navigation-tab, .mobile-navigation-panel { display: none; } .mobile-tabs button.selected { border-color: var(--pi-accent); background: var(--pi-selection-bg); } .tab-badge { display: inline-block; min-width: 14px; margin-left: 4px; border: 1px solid var(--pi-success-border); border-radius: 999px; background: var(--pi-success-surface); color: var(--pi-success); padding: 0 5px; font-size: 11px; line-height: 16px; text-align: center; } - .expand-panel-strip { flex: 0 0 auto; display: flex; align-items: center; justify-content: flex-end; padding: 4px 8px; border-bottom: 1px solid var(--pi-border-muted); } - .expand-workspace-panel-button { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; padding: 0; border-radius: 6px; } - .expand-workspace-panel-button .expand-icon { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; } - workspace-panel { min-width: 0; min-height: 0; border-left: 1px solid var(--pi-border); overflow: hidden; } + .workspace-panel-edge { grid-column: 3; min-width: 0; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: visible; background: var(--pi-border-muted); z-index: 2; } + .workspace-panel-edge-button { position: relative; z-index: 1; display: grid; place-items: center; width: 18px; height: 48px; padding: 0; border: 1px solid var(--pi-border-muted); border-radius: 999px; background: var(--pi-bg); color: var(--pi-muted); opacity: .75; cursor: pointer; } + .workspace-panel-edge-button:hover, .workspace-panel-edge-button:focus-visible { color: var(--pi-text); background: var(--pi-surface-hover); opacity: 1; } + .workspace-panel-edge-icon { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; } + workspace-panel { grid-column: 4; min-width: 0; min-height: 0; overflow: hidden; } + @media (min-width: 1181px) { + .shell.workspace-panel-collapsed { grid-template-columns: 340px minmax(420px, 1fr) 1px; } + .shell.workspace-panel-collapsed > workspace-panel { display: none; } + } @media (max-width: 1180px) { .shell { grid-template-columns: 340px minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); } - .shell.workspace-panel-collapsed { grid-template-columns: 340px minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); } aside { grid-row: 1 / 3; } main { grid-column: 2; grid-row: 1 / 3; } .mobile-tabs-frame { display: flex; } .shell.workspace-view main { grid-row: 1; min-height: auto; } .shell.workspace-view > workspace-panel { grid-column: 2; grid-row: 2; display: flex; border-left: 0; } .shell:not(.workspace-view) > workspace-panel { display: none; } - .shell.workspace-panel-collapsed > workspace-panel { display: none; } - .expand-panel-strip { display: none; } + .workspace-panel-edge { display: none; } main.workspace-view chat-view, main.workspace-view prompt-editor, main.workspace-view status-bar, main.workspace-view .empty { display: none; } main.workspace-view { overflow: hidden; } } @media (max-width: 760px) { .shell { grid-template-columns: minmax(0, 1fr); } - .shell.workspace-panel-collapsed { grid-template-columns: minmax(0, 1fr); } aside { display: none; } main, .shell.workspace-view > workspace-panel { grid-column: 1; } .context-bar { display: flex; } @@ -151,8 +151,6 @@ export const workspacePanelStyles = css` .workspace-header-strip { display: flex; justify-content: space-between; align-items: center; gap: 8px; min-width: 0; padding: 8px; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scrollbar-width: thin; } .tabs { flex: 0 0 auto; display: flex; gap: 6px; align-items: center; } .tabs button { flex: 0 0 auto; white-space: nowrap; } - .collapse-button { display: inline-flex; align-items: center; justify-content: center; width: auto; height: auto; padding: 5px 7px; } - .collapse-icon { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; } button { display: inline-flex; align-items: center; gap: 5px; border: 1px solid var(--pi-border); border-radius: 7px; background: var(--pi-surface); color: var(--pi-text); padding: 5px 7px; cursor: pointer; } button.selected { border-color: var(--pi-accent); background: var(--pi-selection-bg); } .tab-badge { display: inline-block; min-width: 14px; border: 1px solid var(--pi-success-border); border-radius: 999px; background: var(--pi-success-surface); color: var(--pi-success); padding: 0 5px; font-size: 11px; line-height: 16px; text-align: center; } From c0b358bf3dad9a441aaf21bc42a60642b7cb9c09 Mon Sep 17 00:00:00 2001 From: Federico Jaramillo Martinez Date: Thu, 28 May 2026 11:06:45 +0200 Subject: [PATCH 8/8] fix: keep collapsed workspace panel handle visible --- src/client/src/components/shared.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/src/components/shared.ts b/src/client/src/components/shared.ts index 6ac093f..098092e 100644 --- a/src/client/src/components/shared.ts +++ b/src/client/src/components/shared.ts @@ -94,8 +94,9 @@ export const appStyles = css` .mobile-tabs button.selected { border-color: var(--pi-accent); background: var(--pi-selection-bg); } .tab-badge { display: inline-block; min-width: 14px; margin-left: 4px; border: 1px solid var(--pi-success-border); border-radius: 999px; background: var(--pi-success-surface); color: var(--pi-success); padding: 0 5px; font-size: 11px; line-height: 16px; text-align: center; } .workspace-panel-edge { grid-column: 3; min-width: 0; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: visible; background: var(--pi-border-muted); z-index: 2; } - .workspace-panel-edge-button { position: relative; z-index: 1; display: grid; place-items: center; width: 18px; height: 48px; padding: 0; border: 1px solid var(--pi-border-muted); border-radius: 999px; background: var(--pi-bg); color: var(--pi-muted); opacity: .75; cursor: pointer; } + .workspace-panel-edge-button { position: relative; z-index: 1; box-sizing: border-box; display: grid; place-items: center; width: 18px; height: 48px; padding: 0; border: 1px solid var(--pi-border-muted); border-radius: 999px; background: var(--pi-bg); color: var(--pi-muted); opacity: .75; cursor: pointer; } .workspace-panel-edge-button:hover, .workspace-panel-edge-button:focus-visible { color: var(--pi-text); background: var(--pi-surface-hover); opacity: 1; } + .shell.workspace-panel-collapsed .workspace-panel-edge-button { transform: translateX(calc(-50% + .5px)); } .workspace-panel-edge-icon { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; } workspace-panel { grid-column: 4; min-width: 0; min-height: 0; overflow: hidden; } @media (min-width: 1181px) {