diff --git a/packages/ui/src/apps/VSCodeApp.tsx b/packages/ui/src/apps/VSCodeApp.tsx
index 16b7e74e..b3c74248 100644
--- a/packages/ui/src/apps/VSCodeApp.tsx
+++ b/packages/ui/src/apps/VSCodeApp.tsx
@@ -5,6 +5,7 @@ import { RuntimeAPIProvider } from '@/contexts/RuntimeAPIProvider';
import { registerRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
import { TooltipProvider } from '@/components/ui/tooltip';
import { Toaster } from '@/components/ui/sonner';
+import { ConfigUpdateOverlay } from '@/components/ui/ConfigUpdateOverlay';
import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
import { VSCodeLayout } from '@/components/layout/VSCodeLayout';
import { usePushVisibilityBeacon } from '@/hooks/usePushVisibilityBeacon';
@@ -124,6 +125,7 @@ export function VSCodeApp({ apis }: VSCodeAppProps) {
+
diff --git a/packages/ui/src/components/ui/ConfigUpdateOverlay.tsx b/packages/ui/src/components/ui/ConfigUpdateOverlay.tsx
index 1702237f..fe50ad0e 100644
--- a/packages/ui/src/components/ui/ConfigUpdateOverlay.tsx
+++ b/packages/ui/src/components/ui/ConfigUpdateOverlay.tsx
@@ -6,7 +6,7 @@ import {
import { OpenChamberLogo } from "./OpenChamberLogo";
export const ConfigUpdateOverlay: React.FC = () => {
- const [{ isUpdating, message }, setState] = React.useState(() => getConfigUpdateSnapshot());
+ const [{ isUpdating }, setState] = React.useState(() => getConfigUpdateSnapshot());
React.useEffect(() => {
return subscribeConfigUpdate(setState);
@@ -16,12 +16,11 @@ export const ConfigUpdateOverlay: React.FC = () => {
return null;
}
+ // No status text — the update message is internal jargon and reads as noise.
+ // The animated logo alone signals "working".
return (
-
-
-
- {message}
-
+
+
);
};
diff --git a/packages/ui/src/components/ui/OpenChamberLogo.tsx b/packages/ui/src/components/ui/OpenChamberLogo.tsx
index 89e6922b..34969e11 100644
--- a/packages/ui/src/components/ui/OpenChamberLogo.tsx
+++ b/packages/ui/src/components/ui/OpenChamberLogo.tsx
@@ -82,6 +82,7 @@ export const OpenChamberLogo: React.FC
= ({
className = '',
width = 70,
height = 70,
+ isAnimated = false,
}) => {
const { t } = useI18n();
const themeContext = useOptionalThemeSystem();
@@ -193,6 +194,9 @@ export const OpenChamberLogo: React.FC = ({
role="img"
aria-label={t('openChamberLogo.aria.logo')}
>
+ {isAnimated ? (
+
+ ) : null}
{/* Left face - base fill */}
= ({
/>
{/* OpenCode logo on top face */}
-
- {/*
+
+ {/*
Isometric transform for top face:
OpenCode logo (32x40 viewBox) centered and projected to isometric plane
*/}
diff --git a/packages/vscode/src/ChatViewProvider.ts b/packages/vscode/src/ChatViewProvider.ts
index f112006f..9e99d2e7 100644
--- a/packages/vscode/src/ChatViewProvider.ts
+++ b/packages/vscode/src/ChatViewProvider.ts
@@ -306,6 +306,23 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
});
}
+ /**
+ * Ask the webview to run the full OpenCode reload flow (overlay + managed
+ * restart via the bridge + config/data refresh) — the same flow used after an
+ * OpenCode update. Returns false if no webview is resolved to drive it.
+ */
+ public reloadOpenCode(): boolean {
+ if (!this._view) {
+ return false;
+ }
+
+ this._view.webview.postMessage({
+ type: 'command',
+ command: 'reloadOpenCode',
+ });
+ return true;
+ }
+
public notifyWindowFocusChanged(focused: boolean): void {
if (!this._view) {
return;
diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts
index aa351e66..1b429779 100644
--- a/packages/vscode/src/extension.ts
+++ b/packages/vscode/src/extension.ts
@@ -262,6 +262,13 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('openchamber.restartApi', async () => {
try {
+ // Prefer the full in-app reload flow (overlay + managed restart via the
+ // bridge + config/data refresh) driven by the webview — same as after an
+ // OpenCode update. Fall back to a bare manager restart when no webview is
+ // open to drive it.
+ if (chatViewProvider?.reloadOpenCode()) {
+ return;
+ }
await openCodeManager?.restart();
vscode.window.showInformationMessage('OpenChamber: API connection restarted');
} catch (e) {
diff --git a/packages/vscode/src/webviewHtml.ts b/packages/vscode/src/webviewHtml.ts
index 7b157b38..5e9f3b3f 100644
--- a/packages/vscode/src/webviewHtml.ts
+++ b/packages/vscode/src/webviewHtml.ts
@@ -107,6 +107,17 @@ export function getWebviewHtml(options: WebviewHtmlOptions): string {
opacity: 0;
pointer-events: none;
}
+ /* Glow pulse on the OpenCode mark on the cube's top face — signals loading without text. */
+ @keyframes oc-logo-glow {
+ 0%, 100% { filter: drop-shadow(0 0 0 transparent); }
+ 50% { filter: drop-shadow(0 0 4px var(--vscode-foreground)); }
+ }
+ #initial-loading .logo-inner {
+ animation: oc-logo-glow 1.8s ease-in-out infinite;
+ }
+ @media (prefers-reduced-motion: reduce) {
+ #initial-loading .logo-inner { animation: none; }
+ }
/* Logo colors use VS Code foreground color */
#initial-loading .logo-stroke {
stroke: var(--vscode-foreground);
@@ -153,9 +164,8 @@ export function getWebviewHtml(options: WebviewHtmlOptions): string {
-
- ${initialStatus === 'connecting' ? 'Starting OpenCode API…' : initialStatus === 'connected' ? 'Initializing…' : 'Connecting…'}
-
+
+
${!cliAvailable ? `OpenCode CLI not found. Please install it first.
` : ''}
@@ -184,17 +194,13 @@ export function getWebviewHtml(options: WebviewHtmlOptions): string {
if (msg && msg.type === 'connectionStatus') {
var statusEl = document.getElementById('loading-status');
if (statusEl) {
- if (msg.status === 'connecting') {
- statusEl.textContent = 'Starting OpenCode API…';
- statusEl.classList.remove('error-text');
- } else if (msg.status === 'connected') {
- statusEl.textContent = 'Connected!';
- statusEl.classList.remove('error-text');
- } else if (msg.status === 'error') {
+ // Only show text when something is wrong — progress states stay silent
+ // (the animated logo already signals "working").
+ if (msg.status === 'error') {
statusEl.textContent = msg.error || 'Connection error';
statusEl.classList.add('error-text');
} else {
- statusEl.textContent = 'Reconnecting…';
+ statusEl.textContent = '';
statusEl.classList.remove('error-text');
}
}
diff --git a/packages/vscode/webview/main.tsx b/packages/vscode/webview/main.tsx
index 263b0a32..26038643 100644
--- a/packages/vscode/webview/main.tsx
+++ b/packages/vscode/webview/main.tsx
@@ -181,9 +181,8 @@ const maybeHideLoadingOverlay = () => {
return;
}
- const providersText = bootstrapProvidersReady ? '✓ Providers' : '… Providers';
- const agentsText = bootstrapAgentsReady ? '✓ Agents' : '… Agents';
- setLoadingStatusText(`Loading data (${providersText}, ${agentsText})…`);
+ // Still loading providers/agents — stay silent (the animated logo signals work).
+ setLoadingStatusText('');
return;
}
@@ -200,7 +199,8 @@ const maybeHideLoadingOverlay = () => {
return;
}
- setLoadingStatusText('Starting OpenCode API…');
+ // Connecting — no jargon; the animated logo conveys progress.
+ setLoadingStatusText('');
};
const applyInitialTheme = (theme: { metadata?: { variant?: string }; colors?: { surface?: { background?: string; foreground?: string } } }) => {
@@ -1283,6 +1283,15 @@ onCommand('showSettings', () => {
window.dispatchEvent(new CustomEvent('openchamber:navigate', { detail: { view: 'settings' } }));
});
+// Run the same full OpenCode reload flow the app uses after an update: shows the
+// reload overlay, restarts the managed OpenCode (via the bridge's /api/config/reload),
+// and refreshes config/data. Triggered by the "Restart API Connection" command.
+onCommand('reloadOpenCode', () => {
+ void import('@openchamber/ui/stores/useAgentsStore').then(({ reloadOpenCodeConfiguration }) => {
+ void reloadOpenCodeConfiguration();
+ });
+});
+
const getNotificationClaimKey = (payload: { title?: unknown; body?: unknown; sessionId?: unknown; tag?: unknown } | undefined): string => {
const tag = typeof payload?.tag === 'string' ? payload.tag.trim() : '';
if (tag) return tag;