feat(ui): add desktop git sidebar + terminal dock and improve in-app PR workflow (#362)

* feat: add unified dropdown with services content in header

* feat: add right Git sidebar with resizable panel

* feat: implement responsive panel auto-toggle and terminal rehydration

- Auto-close the right sidebar when width is below a threshold and auto-open it when space permits
- Auto-close the bottom terminal when height is below a threshold and auto-open it when enough space
- Apply a dedicated rehydrated streaming configuration for terminal sessions to optimize reconnect behavior

* feat: enhance PR view with status caching and annotations

* feat(ui): enable chat dispatch in PullRequestSection

* feat(TerminalView): adjust layout

* feat: refine chat input layout and text selection menu

* fix(ui): show empty state in GitView when no changes

* feat(git): update PR actions styling and create PR button
This commit is contained in:
Bohdan Triapitsyn
2026-02-09 03:13:34 +02:00
committed by GitHub
parent 3f29b2c6a2
commit 5b0a97d170
30 changed files with 3216 additions and 1443 deletions
+66
View File
@@ -20,6 +20,7 @@ type GitHubChecksSummary = {
type GitHubPullRequest = {
number: number;
title: string;
body?: string;
url: string;
state: 'open' | 'closed' | 'merged';
draft: boolean;
@@ -48,6 +49,13 @@ type GitHubPullRequestCreateInput = {
draft?: boolean;
};
type GitHubPullRequestUpdateInput = {
directory: string;
number: number;
title: string;
body?: string;
};
type GitHubPullRequestMergeInput = {
directory: string;
number: number;
@@ -208,6 +216,7 @@ export const getPullRequestStatus = async (
const pr: GitHubPullRequest = {
number: typeof prJson.number === 'number' ? prJson.number : 0,
title: readString(prJson.title) || '',
body: readString(prJson.body) || '',
url: readString(prJson.html_url) || '',
state,
draft: Boolean(prJson.draft),
@@ -326,6 +335,7 @@ export const createPullRequest = async (
return {
number: typeof json.number === 'number' ? json.number : 0,
title: readString(json.title) || '',
body: readString(json.body) || '',
url: readString(json.html_url) || '',
state: readString(json.state) === 'closed' ? 'closed' : 'open',
draft: Boolean(json.draft),
@@ -337,6 +347,62 @@ export const createPullRequest = async (
};
};
export const updatePullRequest = async (
accessToken: string,
directory: string,
payload: GitHubPullRequestUpdateInput,
): Promise<GitHubPullRequest> => {
const repo = await resolveRepoFromDirectory(directory);
if (!repo) {
throw new Error('Unable to resolve GitHub repo from git remote');
}
const resp = await githubFetch(`${API_BASE}/repos/${repo.owner}/${repo.repo}/pulls/${payload.number}`, accessToken, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: payload.title,
...(typeof payload.body === 'string' ? { body: payload.body } : {}),
}),
});
if (resp.status === 403) {
throw new Error('Not authorized to edit this PR');
}
if (resp.status === 401) {
const error = new Error('unauthorized');
(error as unknown as { status?: number }).status = 401;
throw error;
}
const json = await jsonOrNull<JsonRecord>(resp);
if (!resp.ok || !json) {
const message = readString(json?.message);
const firstError = Array.isArray(json?.errors) && json.errors.length > 0
? readString((json.errors[0] as JsonRecord)?.message || (json.errors[0] as JsonRecord)?.code)
: '';
const details = [message, firstError].filter(Boolean).join(' · ');
throw new Error(details || 'Failed to update PR');
}
const merged = Boolean(json.merged || json.merged_at);
const state = merged ? 'merged' : (readString(json.state) === 'closed' ? 'closed' : 'open');
return {
number: typeof json.number === 'number' ? json.number : payload.number,
title: readString(json.title) || payload.title,
body: readString(json.body) || '',
url: readString(json.html_url) || '',
state,
draft: Boolean(json.draft),
base: readString((json.base as JsonRecord | undefined)?.ref) || '',
head: readString((json.head as JsonRecord | undefined)?.ref) || '',
headSha: readString((json.head as JsonRecord | undefined)?.sha) || undefined,
mergeable: typeof json.mergeable === 'boolean' ? json.mergeable : null,
mergeableState: readString(json.mergeable_state) || undefined,
};
};
export const mergePullRequest = async (
accessToken: string,
directory: string,