From d34e0163e38c6b38f6cd7fa031442968c552ad06 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Wed, 11 Feb 2026 10:51:27 +0000 Subject: [PATCH] fix(ui): keep right panel layout in empty states Render SplitFilePanel consistently and move empty/loading messages into the viewer area so the right drawer keeps its standard layout even when there are no session diffs, no git changes, or files are still loading. --- .../shell/right-panel/tabs/ChangesTab.tsx | 152 +++++++++--------- .../shell/right-panel/tabs/FilesTab.tsx | 22 +-- .../shell/right-panel/tabs/GitChangesTab.tsx | 150 +++++++++-------- 3 files changed, 160 insertions(+), 164 deletions(-) diff --git a/packages/ui/src/components/instance/shell/right-panel/tabs/ChangesTab.tsx b/packages/ui/src/components/instance/shell/right-panel/tabs/ChangesTab.tsx index 08109ead..25a36824 100644 --- a/packages/ui/src/components/instance/shell/right-panel/tabs/ChangesTab.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/tabs/ChangesTab.tsx @@ -32,32 +32,11 @@ interface ChangesTabProps { const ChangesTab: Component = (props) => { const renderContent = (): JSX.Element => { const sessionId = props.activeSessionId() - if (!sessionId || sessionId === "info") { - return ( -
- {props.t("instanceShell.sessionChanges.noSessionSelected")} -
- ) - } - const diffs = props.activeSessionDiffs() - if (diffs === undefined) { - return ( -
- {props.t("instanceShell.sessionChanges.loading")} -
- ) - } + const hasSession = Boolean(sessionId && sessionId !== "info") + const diffs = hasSession ? props.activeSessionDiffs() : null - if (!Array.isArray(diffs) || diffs.length === 0) { - return ( -
- {props.t("instanceShell.sessionChanges.empty")} -
- ) - } - - const sorted = [...diffs].sort((a, b) => String(a.file || "").localeCompare(String(b.file || ""))) + const sorted = Array.isArray(diffs) ? [...diffs].sort((a, b) => String(a.file || "").localeCompare(String(b.file || ""))) : [] const totals = sorted.reduce( (acc, item) => { acc.additions += typeof item.additions === "number" ? item.additions : 0 @@ -67,25 +46,27 @@ const ChangesTab: Component = (props) => { { additions: 0, deletions: 0 }, ) - const mostChanged = sorted.reduce((best, item) => { - const bestAdd = typeof (best as any)?.additions === "number" ? (best as any).additions : 0 - const bestDel = typeof (best as any)?.deletions === "number" ? (best as any).deletions : 0 - const bestScore = bestAdd + bestDel + const mostChanged = sorted.length + ? sorted.reduce((best, item) => { + const bestAdd = typeof (best as any)?.additions === "number" ? (best as any).additions : 0 + const bestDel = typeof (best as any)?.deletions === "number" ? (best as any).deletions : 0 + const bestScore = bestAdd + bestDel - const add = typeof (item as any)?.additions === "number" ? (item as any).additions : 0 - const del = typeof (item as any)?.deletions === "number" ? (item as any).deletions : 0 - const score = add + del + const add = typeof (item as any)?.additions === "number" ? (item as any).additions : 0 + const del = typeof (item as any)?.deletions === "number" ? (item as any).deletions : 0 + const score = add + del - if (score > bestScore) return item - if (score < bestScore) return best - return String(item.file || "").localeCompare(String((best as any)?.file || "")) < 0 ? item : best - }, sorted[0]) + if (score > bestScore) return item + if (score < bestScore) return best + return String(item.file || "").localeCompare(String((best as any)?.file || "")) < 0 ? item : best + }, sorted[0]) + : null // Auto-select the most-changed file if none selected. const currentSelected = props.selectedFile() const selectedFileData = sorted.find((f) => f.file === currentSelected) || mostChanged - const scopeKey = `${props.instanceId}:${sessionId}` + const scopeKey = `${props.instanceId}:${hasSession ? sessionId : "no-session"}` const isBinaryDiff = (item: any) => { const before = typeof item?.before === "string" ? item.before : "" @@ -97,6 +78,13 @@ const ChangesTab: Component = (props) => { return false } + const emptyViewerMessage = () => { + if (!hasSession) return props.t("instanceShell.sessionChanges.noSessionSelected") + if (diffs === undefined) return props.t("instanceShell.sessionChanges.loading") + if (!Array.isArray(diffs) || diffs.length === 0) return props.t("instanceShell.sessionChanges.empty") + return props.t("instanceShell.filesShell.viewerEmpty") + } + const renderViewer = () => (
@@ -109,10 +97,10 @@ const ChangesTab: Component = (props) => {
0} fallback={
- {props.t("instanceShell.filesShell.viewerEmpty")} + {emptyViewerMessage()}
} > @@ -140,59 +128,69 @@ const ChangesTab: Component = (props) => {
) + const renderEmptyList = () => ( +
{emptyViewerMessage()}
+ ) + const renderListPanel = () => ( - - {(item) => ( -
{ - props.onSelectFile(item.file, props.isPhoneLayout()) - }} - > -
-
- {item.file} -
-
- +{item.additions} - -{item.deletions} + 0} fallback={renderEmptyList()}> + + {(item) => ( +
{ + props.onSelectFile(item.file, props.isPhoneLayout()) + }} + > +
+
+ {item.file} +
+
+ +{item.additions} + -{item.deletions} +
-
- )} - + )} + + ) const renderListOverlay = () => ( - - {(item) => ( -
{ - props.onSelectFile(item.file, true) - }} - title={item.file} - > -
-
- {item.file} -
-
- +{item.additions} - -{item.deletions} + 0} fallback={renderEmptyList()}> + + {(item) => ( +
{ + props.onSelectFile(item.file, true) + }} + title={item.file} + > +
+
+ {item.file} +
+
+ +{item.additions} + -{item.deletions} +
-
- )} - + )} + + ) + const headerPath = () => (selectedFileData?.file ? selectedFileData.file : props.t("instanceShell.rightPanel.tabs.changes")) + return ( - - {selectedFileData?.file || ""} + + {headerPath()}
diff --git a/packages/ui/src/components/instance/shell/right-panel/tabs/FilesTab.tsx b/packages/ui/src/components/instance/shell/right-panel/tabs/FilesTab.tsx index dcba9267..73dd56cb 100644 --- a/packages/ui/src/components/instance/shell/right-panel/tabs/FilesTab.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/tabs/FilesTab.tsx @@ -37,15 +37,8 @@ interface FilesTabProps { const FilesTab: Component = (props) => { const renderContent = (): JSX.Element => { - if (props.browserLoading() && props.browserEntries() === null) { - return ( -
- Loading files... -
- ) - } - - const entries = props.browserEntries() || [] + const entriesValue = props.browserEntries() + const entries = entriesValue || [] const sorted = [...entries].sort((a, b) => { const aDir = a.type === "directory" ? 0 : 1 const bDir = b.type === "directory" ? 0 : 1 @@ -57,6 +50,11 @@ const FilesTab: Component = (props) => { const headerDisplayedPath = () => props.browserSelectedPath() || props.browserPath() + const emptyViewerMessage = () => { + if (props.browserLoading() && entriesValue === null) return "Loading files..." + return "Select a file to preview" + } + const renderViewer = () => (
@@ -74,7 +72,7 @@ const FilesTab: Component = (props) => { } fallback={
- Select a file to preview + {emptyViewerMessage()}
} > @@ -114,6 +112,10 @@ const FilesTab: Component = (props) => { )} + +
Loading files...
+
+ {(item) => (
= (props) => { const renderContent = (): JSX.Element => { const sessionId = props.activeSessionId() - if (!sessionId || sessionId === "info") { - return ( -
- Select a session to view changes. -
- ) - } - const entries = props.entries() - if (entries === null) { - return ( -
- Loading git changes… -
- ) - } + const hasSession = Boolean(sessionId && sessionId !== "info") + const entries = hasSession ? props.entries() : null - const nonDeleted = entries.filter((item) => item && item.status !== "deleted") - if (nonDeleted.length === 0) { - return ( -
- No git changes yet. -
- ) - } + const sorted = Array.isArray(entries) + ? [...entries].sort((a, b) => String(a.path || "").localeCompare(String(b.path || ""))) + : [] - const sorted = [...entries].sort((a, b) => String(a.path || "").localeCompare(String(b.path || ""))) const totals = sorted.reduce( (acc, item) => { acc.additions += typeof item.added === "number" ? item.added : 0 @@ -82,6 +63,15 @@ const GitChangesTab: Component = (props) => { { additions: 0, deletions: 0 }, ) + const nonDeleted = sorted.filter((item) => item && item.status !== "deleted") + + const emptyViewerMessage = () => { + if (!hasSession) return "Select a session to view changes." + if (entries === null) return "Loading git changes…" + if (nonDeleted.length === 0) return "No git changes yet." + return "No file selected." + } + const selectedPath = props.selectedPath() const fallbackPath = props.mostChangedPath() const selectedEntry = @@ -120,7 +110,7 @@ const GitChangesTab: Component = (props) => { } fallback={
- No file selected. + {emptyViewerMessage()}
} > @@ -153,71 +143,77 @@ const GitChangesTab: Component = (props) => {
) + const renderEmptyList = () =>
{emptyViewerMessage()}
+ const renderListPanel = () => ( - - {(item) => ( -
{ - props.onOpenFile(item.path) - }} - > -
-
- {item.path} -
-
- - deleted - - - <> - +{item.added} - -{item.removed} - - + 0} fallback={renderEmptyList()}> + + {(item) => ( +
{ + props.onOpenFile(item.path) + }} + > +
+
+ {item.path} +
+
+ + deleted + + + <> + +{item.added} + -{item.removed} + + +
-
- )} - + )} + + ) const renderListOverlay = () => ( - - {(item) => ( -
props.onOpenFile(item.path)} - title={item.path} - > -
-
- {item.path} -
-
- - deleted - - - <> - +{item.added} - -{item.removed} - - + 0} fallback={renderEmptyList()}> + + {(item) => ( +
props.onOpenFile(item.path)} + title={item.path} + > +
+
+ {item.path} +
+
+ + deleted + + + <> + +{item.added} + -{item.removed} + + +
-
- )} - + )} + + ) return ( - - {selectedEntry?.path || ""} + + {selectedEntry?.path || "Git Changes"}
@@ -235,7 +231,7 @@ const GitChangesTab: Component = (props) => { class="files-header-icon-button" title={props.t("instanceShell.rightPanel.actions.refresh")} aria-label={props.t("instanceShell.rightPanel.actions.refresh")} - disabled={props.statusLoading()} + disabled={!hasSession || props.statusLoading() || entries === null} style={{ "margin-left": "auto" }} onClick={() => props.onRefresh()} >