Skip to content

Commit abb3c84

Browse files
committed
New User Experience, fixes #7.
1 parent 9baf93e commit abb3c84

7 files changed

Lines changed: 265 additions & 19 deletions

File tree

electron/main.cjs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const {
55
existsSync,
66
lstatSync,
77
mkdirSync,
8+
realpathSync,
89
readFileSync,
910
symlinkSync,
1011
unlinkSync,
@@ -78,8 +79,12 @@ const parseCommandLineArguments = (commandLine = process.argv) => {
7879

7980
const envCommitRef = useEnvironment ? process.env.CODIFF_COMMIT_REF || '' : '';
8081
const sourceRef = envCommitRef || commitRef;
82+
const repositoryPathProvided = Boolean(
83+
repositoryPath || (useEnvironment && process.env.CODIFF_REPOSITORY_PATH),
84+
);
8185
return {
8286
launchOptions: {
87+
repositoryPathProvided,
8388
source: sourceRef
8489
? {
8590
ref: sourceRef,
@@ -196,13 +201,63 @@ const openRepositoryFolder = async (browserWindow) => {
196201
});
197202

198203
if (!result.canceled && result.filePaths[0]) {
199-
createWindow(result.filePaths[0], { walkthrough: false });
204+
createWindow(result.filePaths[0], { repositoryPathProvided: true, walkthrough: false });
200205
}
201206
};
202207

203208
const getTerminalHelperSourcePath = () =>
204209
app.isPackaged ? join(process.resourcesPath, 'app/bin/codiff-app') : join(root, 'bin/codiff.js');
205210

211+
const getTerminalHelperTargetPaths = () => [
212+
'/opt/homebrew/bin/codiff',
213+
'/usr/local/bin/codiff',
214+
join(app.getPath('home'), '.local/bin/codiff'),
215+
];
216+
217+
const getPreferredTerminalHelperTargetPath = () => {
218+
for (const directory of ['/opt/homebrew/bin', '/usr/local/bin']) {
219+
try {
220+
if (existsSync(directory)) {
221+
accessSync(directory, constants.W_OK);
222+
return join(directory, 'codiff');
223+
}
224+
} catch {
225+
// Keep looking for a writable install location.
226+
}
227+
}
228+
229+
return join(app.getPath('home'), '.local/bin/codiff');
230+
};
231+
232+
const isInstalledTerminalHelper = (targetPath) => {
233+
try {
234+
if (!existsSync(targetPath)) {
235+
return false;
236+
}
237+
238+
const target = lstatSync(targetPath);
239+
if (!target.isSymbolicLink()) {
240+
return false;
241+
}
242+
243+
return realpathSync(targetPath) === realpathSync(getTerminalHelperSourcePath());
244+
} catch {
245+
return false;
246+
}
247+
};
248+
249+
const getTerminalHelperStatus = () => {
250+
const installedPath = getTerminalHelperTargetPaths().find((targetPath) =>
251+
isInstalledTerminalHelper(targetPath),
252+
);
253+
254+
return {
255+
command: 'codiff',
256+
installed: installedPath != null,
257+
path: installedPath || getPreferredTerminalHelperTargetPath(),
258+
};
259+
};
260+
206261
const getWritableHelperDirectory = () => {
207262
for (const directory of ['/opt/homebrew/bin', '/usr/local/bin']) {
208263
try {
@@ -246,13 +301,15 @@ const installTerminalHelper = async (browserWindow) => {
246301
message: `Installed codiff at ${targetPath}.`,
247302
type: 'info',
248303
});
304+
return true;
249305
} catch (error) {
250306
await dialog.showMessageBox(browserWindow ?? undefined, {
251307
buttons: ['OK'],
252308
detail: error instanceof Error ? error.message : String(error),
253309
message: 'Could not install the terminal helper.',
254310
type: 'error',
255311
});
312+
return false;
256313
}
257314
};
258315

@@ -392,7 +449,10 @@ const buildApplicationMenu = () =>
392449
},
393450
]);
394451

395-
const createWindow = (repositoryPath, launchOptions = { walkthrough: false }) => {
452+
const createWindow = (
453+
repositoryPath,
454+
launchOptions = { repositoryPathProvided: true, walkthrough: false },
455+
) => {
396456
const display = screen.getPrimaryDisplay();
397457
const { height, width } = display.workAreaSize;
398458
const window = new BrowserWindow({
@@ -491,10 +551,18 @@ ipcMain.handle(
491551
'codiff:getLaunchOptions',
492552
(event) =>
493553
windowLaunchOptions.get(event.sender.id) || {
554+
repositoryPathProvided: false,
494555
walkthrough: false,
495556
},
496557
);
497558

559+
ipcMain.handle('codiff:getTerminalHelperStatus', () => getTerminalHelperStatus());
560+
561+
ipcMain.handle('codiff:installTerminalHelper', async (event) => {
562+
await installTerminalHelper(BrowserWindow.fromWebContents(event.sender));
563+
return getTerminalHelperStatus();
564+
});
565+
498566
ipcMain.handle('codiff:getWalkthrough', async (event, source) => {
499567
const repositoryPath = windowRepositories.get(event.sender.id) || getLaunchPath();
500568
const launchOptions = windowLaunchOptions.get(event.sender.id);

electron/preload.cjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ contextBridge.exposeInMainWorld('codiff', {
88
getPreferences: () => ipcRenderer.invoke('codiff:getPreferences'),
99
getRepositoryHistory: (limit) => ipcRenderer.invoke('codiff:getRepositoryHistory', limit),
1010
getRepositoryState: (source) => ipcRenderer.invoke('codiff:getRepositoryState', source),
11+
getTerminalHelperStatus: () => ipcRenderer.invoke('codiff:getTerminalHelperStatus'),
1112
getWalkthrough: (source) => ipcRenderer.invoke('codiff:getWalkthrough', source),
13+
installTerminalHelper: () => ipcRenderer.invoke('codiff:installTerminalHelper'),
1214
onFindInDiffs: (callback) => {
1315
const listener = () => callback();
1416
ipcRenderer.on('codiff:findInDiffs', listener);

src/App.css

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,19 +1334,54 @@
13341334
border: 1px solid var(--file-border);
13351335
display: flex;
13361336
flex-direction: column;
1337-
gap: 6px;
1337+
gap: 10px;
13381338
max-width: min(560px, 84vw);
13391339
padding: 28px;
13401340
text-align: center;
13411341
}
13421342

1343-
.empty-panel span {
1343+
.empty-panel p {
13441344
color: var(--muted);
1345-
font-family: var(--font-mono);
1346-
font-size: 12px;
1345+
font-size: 13px;
1346+
line-height: 1.45;
1347+
margin: 0;
13471348
overflow-wrap: anywhere;
13481349
}
13491350

1351+
.empty-panel code {
1352+
color: var(--text);
1353+
}
1354+
1355+
.empty-panel-menu-path {
1356+
color: var(--text);
1357+
font-weight: 600;
1358+
}
1359+
1360+
.empty-panel-actions {
1361+
-webkit-app-region: no-drag;
1362+
display: flex;
1363+
gap: 8px;
1364+
margin-top: 8px;
1365+
}
1366+
1367+
.empty-panel-actions button {
1368+
background: var(--text);
1369+
border: 0;
1370+
border-radius: 14px;
1371+
color: var(--app-bg);
1372+
corner-shape: squircle;
1373+
cursor: pointer;
1374+
font-size: 13px;
1375+
font-weight: 700;
1376+
min-height: 34px;
1377+
padding: 0 14px;
1378+
}
1379+
1380+
.empty-panel-actions button:disabled {
1381+
cursor: default;
1382+
opacity: 0.58;
1383+
}
1384+
13501385
@media (max-width: 880px) {
13511386
.app-shell {
13521387
grid-template-columns: 220px minmax(0, 1fr);

src/App.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
buildReviewCommentsMarkdown,
44
fileHasVisibleDiff,
55
getDiffSearchResult,
6+
getRepositoryLoadError,
67
getVisibleDiffSections,
78
isDiffSearchShortcut,
89
shouldDiscardReviewCommentOnEscape,
@@ -126,6 +127,18 @@ test('diff search shortcut does not claim fullscreen shortcut', () => {
126127
);
127128
});
128129

130+
test('repository load errors hide raw git output for non-repositories', () => {
131+
const error = getRepositoryLoadError(
132+
new Error('Command failed: git -C /tmp rev-parse --show-toplevel fatal: not a git repository'),
133+
);
134+
135+
expect(error).toEqual({
136+
kind: 'not-a-repository',
137+
message:
138+
'Codiff was opened outside a Git repository. Run `codiff` from inside a repo, or choose File → Open Folder… to open one.',
139+
});
140+
});
141+
129142
test('review comment markdown includes file and patch context', () => {
130143
const file = {
131144
fingerprint: 'comment-export',

0 commit comments

Comments
 (0)