feat: vscode extension (#59)

* feat: add initial VS Code extension plan and implementation tasks

* feat(vscode): added initial version of an Openchamber VSCode extension

* feat(vscode): enhance VS Code extension with theme integration and session management

* feat(vscode): implement connection status handling and overlay in VSCode layout

* feat: move extension to secondary sidebar

* chore: upgrade @opencode-ai/sdk to 1.0.150

* vscode: editor bridge, file picker, click-to-open in tool parts

* vscode: layout session lifecycle, theme sync, typography overrides

* ui: compact mode for vscode, model search, autocomplete width fixes

* perf: scroll force flag, raf placeholder, git polling backoff

* ui: tool output styling, markdown code block fix, gitignore

* refactor: update typography handling for VSCode runtime, remove unused styles

* docs: update README with VS Code extension details and add extension image

* docs: update changelog with new features and performance improvements
This commit is contained in:
Bohdan Triapitsyn
2025-12-13 16:34:17 +02:00
committed by GitHub
parent 610ccf4c62
commit bb72c0fb0c
76 changed files with 6097 additions and 296 deletions
+46 -13
View File
@@ -7,7 +7,9 @@ import type {
GitIdentitySummary,
} from '@/lib/api/types';
const GIT_POLL_INTERVAL = 3000;
const GIT_POLL_BASE_INTERVAL = 10000;
const GIT_POLL_MAX_INTERVAL = 20000;
const GIT_POLL_BACKOFF_STEP = 5000;
const LOG_STALE_THRESHOLD = 30000;
interface DirectoryGitState {
@@ -34,7 +36,8 @@ interface GitStore {
isLoadingBranches: boolean;
isLoadingIdentity: boolean;
pollIntervalId: ReturnType<typeof setInterval> | null;
pollIntervalId: ReturnType<typeof setTimeout> | null;
currentPollInterval: number;
setActiveDirectory: (directory: string | null) => void;
getDirectoryState: (directory: string) => DirectoryGitState | null;
@@ -139,6 +142,7 @@ export const useGitStore = create<GitStore>()(
isLoadingBranches: false,
isLoadingIdentity: false,
pollIntervalId: null,
currentPollInterval: GIT_POLL_BASE_INTERVAL,
setActiveDirectory: (directory) => {
const { activeDirectory, directories } = get();
@@ -346,24 +350,53 @@ export const useGitStore = create<GitStore>()(
const { pollIntervalId } = get();
if (pollIntervalId) return;
const intervalId = setInterval(async () => {
const { activeDirectory } = get();
if (!activeDirectory) return;
const schedulePoll = () => {
const { currentPollInterval } = get();
const timeoutId = setTimeout(async () => {
// Skip if tab not visible
if (typeof document !== 'undefined' && document.hidden) {
set({ pollIntervalId: schedulePoll() });
return;
}
const statusChanged = await get().fetchStatus(activeDirectory, git, { silent: true });
if (statusChanged) {
await get().fetchLog(activeDirectory, git);
}
}, GIT_POLL_INTERVAL);
const { activeDirectory } = get();
if (!activeDirectory) {
set({ pollIntervalId: schedulePoll() });
return;
}
set({ pollIntervalId: intervalId });
const statusChanged = await get().fetchStatus(activeDirectory, git, { silent: true });
if (statusChanged) {
await get().fetchLog(activeDirectory, git);
// Reset to base interval on changes
set({ currentPollInterval: GIT_POLL_BASE_INTERVAL });
} else {
// Backoff when no changes
const newInterval = Math.min(
currentPollInterval + GIT_POLL_BACKOFF_STEP,
GIT_POLL_MAX_INTERVAL
);
set({ currentPollInterval: newInterval });
}
// Schedule next poll
const { pollIntervalId: currentId } = get();
if (currentId !== null) {
set({ pollIntervalId: schedulePoll() });
}
}, currentPollInterval);
return timeoutId;
};
set({ pollIntervalId: schedulePoll(), currentPollInterval: GIT_POLL_BASE_INTERVAL });
},
stopPolling: () => {
const { pollIntervalId } = get();
if (pollIntervalId) {
clearInterval(pollIntervalId);
set({ pollIntervalId: null });
clearTimeout(pollIntervalId);
set({ pollIntervalId: null, currentPollInterval: GIT_POLL_BASE_INTERVAL });
}
},