fix(FN-XXX): improve git manager diff layout

This commit is contained in:
gsxdsm
2026-04-29 16:26:53 -07:00
parent c6d67b9e1c
commit 291e156b26
5 changed files with 141 additions and 31 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Improve the Git Manager diff layout and file path truncation in dashboard modals.

View File

@@ -220,7 +220,7 @@ export function CommitDiffTab({ commitSha, mergeDetails }: CommitDiffTabProps) {
{getStatusLabel(file.status)} {getStatusLabel(file.status)}
</span> </span>
<span className="changes-file-path" title={file.path}> <span className="changes-file-path" title={file.path}>
{file.path} <bdo dir="ltr">{file.path}</bdo>
</span> </span>
<span <span
className="changes-file-stat" className="changes-file-stat"

View File

@@ -4,7 +4,6 @@ import type { Task } from "@fusion/core";
import { getErrorMessage } from "@fusion/core"; import { getErrorMessage } from "@fusion/core";
import type { ToastType } from "../hooks/useToast"; import type { ToastType } from "../hooks/useToast";
import { useConfirm } from "../hooks/useConfirm"; import { useConfirm } from "../hooks/useConfirm";
import { truncateMiddle } from "../utils/truncatePath";
import { getPathBasename } from "../utils/pathDisplay"; import { getPathBasename } from "../utils/pathDisplay";
import { useModalResizePersist } from "../hooks/useModalResizePersist"; import { useModalResizePersist } from "../hooks/useModalResizePersist";
import { useOverlayDismiss } from "../hooks/useOverlayDismiss"; import { useOverlayDismiss } from "../hooks/useOverlayDismiss";
@@ -1054,6 +1053,8 @@ function ChangesPanel({
</div> </div>
)} )}
<div className="gm-changes-split">
<div className="gm-changes-lists">
{/* Unstaged Changes */} {/* Unstaged Changes */}
<div className="gm-file-section"> <div className="gm-file-section">
<div className="gm-file-section-header"> <div className="gm-file-section-header">
@@ -1116,7 +1117,7 @@ function ChangesPanel({
/> />
</label> </label>
<FileStatusIcon status={f.status} /> <FileStatusIcon status={f.status} />
<span className="gm-file-name" title={f.file}>{truncateMiddle(f.file, 40)}</span> <span className="gm-file-name" title={f.file}><bdo dir="ltr">{f.file}</bdo></span>
<FileStatusBadge status={f.status} /> <FileStatusBadge status={f.status} />
<button <button
className="gm-icon-btn" className="gm-icon-btn"
@@ -1188,7 +1189,7 @@ function ChangesPanel({
/> />
</label> </label>
<FileStatusIcon status={f.status} /> <FileStatusIcon status={f.status} />
<span className="gm-file-name" title={f.file}>{truncateMiddle(f.file, 40)}</span> <span className="gm-file-name" title={f.file}><bdo dir="ltr">{f.file}</bdo></span>
<FileStatusBadge status={f.status} /> <FileStatusBadge status={f.status} />
<button <button
className="gm-icon-btn" className="gm-icon-btn"
@@ -1207,8 +1208,10 @@ function ChangesPanel({
</div> </div>
</div> </div>
{/* Diff Viewer */} </div>
{(selectedDiffTarget || loadingChangeDiff || changeDiff || changeDiffError) && ( {/* Diff Viewer (right pane on desktop, stacked below on mobile) */}
<div className="gm-changes-diff">
{(selectedDiffTarget || loadingChangeDiff || changeDiff || changeDiffError) ? (
<div className="gm-diff-section"> <div className="gm-diff-section">
{selectedDiffTarget && ( {selectedDiffTarget && (
<div className="gm-diff-target"> <div className="gm-diff-target">
@@ -1233,7 +1236,15 @@ function ChangesPanel({
</div> </div>
)} )}
</div> </div>
) : (
<div className="gm-diff-empty">
<FileDiff size={20} />
<span>Select a file to view its diff</span>
</div>
)} )}
</div>
</div>
{/* /gm-changes-split */}
{/* Commit Form */} {/* Commit Form */}
<form className="gm-commit-form" onSubmit={onCommit}> <form className="gm-commit-form" onSubmit={onCommit}>

View File

@@ -2035,6 +2035,86 @@
color: var(--color-error); color: var(--color-error);
} }
/* ── Split-pane: file lists on left, diff viewer on right ─────────────────
* Lazygit-style layout so the file lists stay visible while reading a diff.
* `.gm-content` (the panel scroll container) keeps overflow-y: auto, and the
* left column uses `position: sticky` so it pins to the top as the right
* column scrolls. Mobile (≤720px) collapses to a single column with the
* lists rendered first and the diff below — sticky disabled because there's
* no horizontal room for a second pane and a sticky list would eat the
* already-tight viewport.
*/
.gm-changes-split {
display: grid;
grid-template-columns: minmax(280px, 360px) 1fr;
gap: var(--space-md);
align-items: start;
}
.gm-changes-lists {
display: flex;
flex-direction: column;
gap: var(--space-md);
position: sticky;
top: 0;
align-self: start;
/* Cap the sticky column so its internal lists never push past the viewport
* — without this, two long lists at max-height 200px each + headers can
* exceed the available scroll-port height and the column visually clips. */
max-height: calc(100dvh - var(--overlay-padding-top, 10vh) - 280px);
overflow-y: auto;
/* Don't let the sticky scrollbar overlap the diff column. */
padding-right: 2px;
}
.gm-changes-diff {
display: flex;
flex-direction: column;
gap: var(--space-sm);
/* Allow children (long file paths, code lines) to shrink below intrinsic
* width — required for `overflow: hidden` / `word-break` to work in the
* diff viewer. Without this the right column would push the layout wider
* than the modal. */
min-width: 0;
/* Match the lists' top so empty/loading/diff states align with the list
* column visually. */
padding-top: 0;
}
/* Empty placeholder shown when no file is selected. Gives the right pane
* structure on first open instead of a blank gap. */
.gm-diff-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-sm);
padding: var(--space-2xl) var(--space-lg);
border: 1px dashed var(--border);
border-radius: var(--radius-md);
color: var(--text-dim);
font-size: 13px;
min-height: 140px;
}
@media (max-width: 720px) {
.gm-changes-split {
grid-template-columns: 1fr;
}
.gm-changes-lists {
position: static;
max-height: none;
overflow-y: visible;
padding-right: 0;
}
.gm-diff-empty {
/* Don't take a full screen of empty state on mobile — collapse to a
* compact hint when no file is selected. */
min-height: 0;
padding: var(--space-md);
}
}
/* ── File Section ── */ /* ── File Section ── */
.gm-file-section { .gm-file-section {
@@ -2118,11 +2198,18 @@
accent-color: var(--todo); accent-color: var(--todo);
} }
/* Show as much of the path as fits, truncating the LEFT side (parent dirs)
* with an ellipsis so the filename — the most identifying part — stays
* visible. `direction: rtl` flips the truncation side; the JSX wraps the
* value in `<bdo dir="ltr">` so the displayed text still reads naturally
* left-to-right. */
.gm-file-name { .gm-file-name {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis;
direction: rtl;
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 12px; font-size: 12px;
color: var(--text); color: var(--text);
@@ -3555,6 +3642,8 @@
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis;
direction: rtl;
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
} }

View File

@@ -1087,11 +1087,16 @@
margin-left: calc(16px + var(--space-sm)); /* align with text after icon + gap */ margin-left: calc(16px + var(--space-sm)); /* align with text after icon + gap */
} }
/* Truncate parent dirs from the LEFT so the filename stays visible. JSX
* wraps the path in `<bdo dir="ltr">` to preserve readable left-to-right
* display while `direction: rtl` flips the ellipsis to the start. */
.changes-file-path { .changes-file-path {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis;
direction: rtl;
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 12px; font-size: 12px;
} }