Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4687,6 +4687,45 @@ describe("Git commit sidebar", () => {
expect(commitButton).toBeDisabled();
});

it("explains why Cmd+Enter did nothing and clears the hint once the user acts", async () => {
render(<App />);

expect(await treeButton("README.md")).toBeInTheDocument();
fireEvent.click(screen.getByTitle("Source control"));

const panel = await screen.findByLabelText("Git commit panel");
await waitFor(() => expect(within(panel).getByText("2 / 2")).toBeInTheDocument());
const message = within(panel).getByPlaceholderText("Commit message");

fireEvent.click(within(panel).getByLabelText("Deselect all changes"));
fireEvent.change(message, { target: { value: "Update readme" } });
fireEvent.keyDown(message, { key: "Enter", metaKey: true });
expect(
await within(panel).findByText("Select at least one file to commit."),
).toBeInTheDocument();

fireEvent.click(within(panel).getByLabelText("Select all changes"));
await waitFor(() =>
expect(
within(panel).queryByText("Select at least one file to commit."),
).not.toBeInTheDocument(),
);

fireEvent.change(message, { target: { value: " " } });
// Ctrl instead of Cmd here so both halves of metaKey || ctrlKey get covered.
fireEvent.keyDown(message, { key: "Enter", ctrlKey: true });
expect(
await within(panel).findByText("Enter a commit message first."),
).toBeInTheDocument();

fireEvent.change(message, { target: { value: "Update readme" } });
await waitFor(() =>
expect(
within(panel).queryByText("Enter a commit message first."),
).not.toBeInTheDocument(),
);
});
Comment thread
GordonBeeming marked this conversation as resolved.

it("commits only the selected paths and refreshes the status afterward", async () => {
render(<App />);

Expand Down
41 changes: 40 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,9 @@ export default function App() {
const [gitCommitInFlight, setGitCommitInFlight] = useState(false);
const [gitCommitError, setGitCommitError] = useState<string>();
const [gitCommitSuccess, setGitCommitSuccess] = useState<string>();
// Why-nothing-happened hint for Cmd/Ctrl+Enter in the message box — the
// Commit button disables itself, but the shortcut has no visual state.
const [gitCommitHint, setGitCommitHint] = useState<string>();
const [gitSyncInFlight, setGitSyncInFlight] = useState(false);
const [gitSyncResult, setGitSyncResult] = useState<GitSyncResult>();
const [gitSyncError, setGitSyncError] = useState<string>();
Expand Down Expand Up @@ -1652,6 +1655,19 @@ export default function App() {
return () => window.clearTimeout(timeoutId);
}, [gitCommitSuccess]);

// The commit hint answers "why did Cmd+Enter do nothing" — the moment the
// user changes the message or the file selection, the answer is stale.
// Keyed on a sorted-path signature rather than the Set itself: background
// status polls rebuild the Set with identical contents, and clearing on
// reference identity would dismiss the hint without any user action.
const gitCommitSelectionSignature = useMemo(
() => [...gitCommitSelectedPaths].sort().join("\n"),
[gitCommitSelectedPaths],
);
Comment thread
GordonBeeming marked this conversation as resolved.
useEffect(() => {
setGitCommitHint(undefined);
}, [gitCommitMessage, gitCommitSelectionSignature]);
Comment thread
GordonBeeming marked this conversation as resolved.

// Sync notices are scoped to a commit-panel session; clear them on the way out
// so reopening the panel doesn't show a stale "Synced"/conflict line.
useEffect(() => {
Expand All @@ -1660,6 +1676,7 @@ export default function App() {
setGitSyncError(undefined);
setGitMergeError(undefined);
setGitMergeSuccess(undefined);
setGitCommitHint(undefined);
}, [commitModeActive]);

// Clear the "Completed merge" line after a beat, matching the commit-success
Expand Down Expand Up @@ -1736,8 +1753,24 @@ export default function App() {
const handleGitCommit = useCallback(async () => {
const trimmedMessage = gitCommitMessage.trim();
const selectedPaths = changedFilePaths.filter((path) => gitCommitSelectedPaths.has(path));
if (!trimmedMessage || selectedPaths.length === 0 || gitCommitInFlight) return;
if (gitCommitInFlight) return;
// A failed precondition also clears the previous attempt's outcome lines —
// a leftover "Committed …" or error next to the warning reads as if this
// attempt did something.
if (selectedPaths.length === 0) {
setGitCommitError(undefined);
setGitCommitSuccess(undefined);
setGitCommitHint("Select at least one file to commit.");
return;
}
if (!trimmedMessage) {
setGitCommitError(undefined);
setGitCommitSuccess(undefined);
setGitCommitHint("Enter a commit message first.");
return;
}

setGitCommitHint(undefined);
setGitCommitInFlight(true);
setGitCommitError(undefined);
setGitCommitSuccess(undefined);
Expand Down Expand Up @@ -5453,6 +5486,12 @@ export default function App() {
{gitCommitSuccess}
</div>
) : null}
{gitCommitHint ? (
<div className="commit-panel__notice commit-panel__notice--warning" role="status">
<TriangleAlert size={13} />
<span>{gitCommitHint}</span>
</div>
) : null}
</div>
</>
)}
Expand Down
27 changes: 14 additions & 13 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,20 @@ button {
color: var(--text);
}

.commit-panel__notice--warning {
display: flex;
align-items: center;
gap: 6px;
border: 1px solid color-mix(in oklch, var(--warning) 42%, var(--border));
background: color-mix(in oklch, var(--warning) 8%, var(--surface));
color: var(--text);
}

.commit-panel__notice--warning svg {
flex-shrink: 0;
color: var(--warning);
}

/* Transient success toasts (sync result, completed merge) hold, then ease out
over the last stretch of their ~5s life so removal doesn't just blink away.
The JS timeout clears the element at 5s; this only animates the opacity. */
Expand Down Expand Up @@ -1110,19 +1124,6 @@ button {
font-weight: 600;
}

.commit-panel__sync-conflicts {
margin: 0;
padding-left: 18px;
display: flex;
flex-direction: column;
gap: 2px;
}

.commit-panel__sync-conflicts li {
overflow-wrap: anywhere;
word-break: break-word;
}

/* Live merge-resolution block: a bordered call-out that replaces the normal
sync notice while a merge is unfinished. */
.commit-panel__merge {
Expand Down
Loading