feat(ui): unify change comparisons and compact message actions

Branch comparisons could retain an old base or omit local edits, while Changes and walkthrough selected their sources independently.

Share branch and commit selectors across both panels, honor exact refs, include local branch edits, and support first-parent commit diffs with the latest 50 commits. Compact message metadata and move touch actions into a shared sheet.

Validated with workspace type-check, lint and build, focused Git and UI tests, and maintainer testing in the app.
This commit is contained in:
Bohdan Triapitsyn
2026-09-09 17:41:14 +03:00
parent ec7db447bd
commit 0b899d1153
45 changed files with 2131 additions and 599 deletions
+24 -4
View File
@@ -7,13 +7,13 @@ export function registerGitRoutes(app) {
return gitLibraries;
};
const resolveDirectoryQuery = (value) => {
const resolveDirectoryQuery = (value, preserveWhitespace = false) => {
const raw = Array.isArray(value) ? value[0] : value;
if (typeof raw !== 'string') {
return null;
}
const trimmed = raw.trim();
return trimmed || null;
const normalized = preserveWhitespace ? raw : raw.trim();
return normalized || null;
};
const extractGitErrorText = (error) => {
@@ -417,6 +417,7 @@ export function registerGitRoutes(app) {
const diff = await getRangeDiff(directory, {
base,
head,
includeWorkingTree: req.query.includeWorkingTree === 'true',
path: pathParam,
contextLines: Number.isFinite(context) ? context : 3,
});
@@ -463,7 +464,7 @@ export function registerGitRoutes(app) {
return res.status(400).json({ error: 'base and head parameters are required' });
}
const files = await getRangeFiles(directory, { base, head });
const files = await getRangeFiles(directory, { base, head, includeWorkingTree: req.query.includeWorkingTree === 'true' });
res.json({ files });
} catch (error) {
console.error('Failed to get git range files:', error);
@@ -1300,6 +1301,25 @@ export function registerGitRoutes(app) {
}
});
app.get('/api/git/commit-diff', async (req, res) => {
const { getCommitDiff } = await getGitLibraries();
try {
const directory = resolveDirectoryQuery(req.query.directory);
const hash = resolveDirectoryQuery(req.query.hash);
if (!directory || !hash) return res.status(400).json({ error: 'directory and hash are required' });
const context = Number(req.query.context ?? 3);
const diff = await getCommitDiff(directory, {
hash,
path: resolveDirectoryQuery(req.query.path, true) ?? undefined,
previousPath: resolveDirectoryQuery(req.query.previousPath, true) ?? undefined,
contextLines: Number.isFinite(context) ? context : 3,
});
res.json({ diff });
} catch (error) {
res.status(500).json({ error: error.message || 'Failed to get commit diff' });
}
});
app.get('/api/git/commit-file-diff', async (req, res) => {
const { getCommitFileDiff } = await getGitLibraries();
try {