diff --git a/packages/ui/src/components/chat/__tests__/issue-2903-subagent-status-line-only.test.tsx b/packages/ui/src/components/chat/__tests__/issue-2903-subagent-status-line-only.test.tsx index 54ed84d2..3b314a22 100644 --- a/packages/ui/src/components/chat/__tests__/issue-2903-subagent-status-line-only.test.tsx +++ b/packages/ui/src/components/chat/__tests__/issue-2903-subagent-status-line-only.test.tsx @@ -138,14 +138,27 @@ const buildMaterializedSubagentSession = () => { return { messages, part }; }; -const syncContext = (globalThis as unknown as { +// SAFETY: sync-context.tsx publishes exactly these two keys on globalThis +// (SYNC_CONTEXT_GLOBAL_KEY / SYNC_RUNTIME_CONTEXT_GLOBAL_KEY) so every module +// instance shares one context identity; the cast only adds those two optional +// keys to the global object type, and the guards below re-check presence. +const syncGlobals = globalThis as { __openchamber_sync_context__?: React.Context; -}).__openchamber_sync_context__; + __openchamber_sync_runtime_context__?: React.Context; +}; + +const syncContext = syncGlobals.__openchamber_sync_context__; if (!syncContext) { throw new Error('sync context was not published on globalThis by @/sync/sync-context'); } +const syncRuntimeContext = syncGlobals.__openchamber_sync_runtime_context__; + +if (!syncRuntimeContext) { + throw new Error('sync runtime context was not published on globalThis by @/sync/sync-context'); +} + describe('issue #2903 busy embedded subagent status-line-only', () => { test('cold disabled reads hide a fully materialized 14-message subagent; enabled reads return all 14', async () => { const dom = installMinimalDom(); @@ -173,7 +186,16 @@ describe('issue #2903 busy embedded subagent status-line-only', () => { }); const system = { childStores, messageLoader: {}, sdk: {}, runtimeKey: 'test', directory: DIRECTORY }; - const Provider = syncContext.Provider as React.Provider; + // Mirrors SyncProvider's own nesting: system context outer, runtime inner. + // Directory-scoped hooks read the runtime context, so the harness must + // provide it with a currentDirectory source for the store lookups. + const runtime = { + childStores, + messageLoader: {}, + sdk: {}, + runtimeKey: 'test', + currentDirectory: { get: () => DIRECTORY, subscribe: () => () => undefined }, + }; let inactiveCount = -1; let activeCount = -1; let enabled = false; @@ -188,15 +210,22 @@ describe('issue #2903 busy embedded subagent status-line-only', () => { return null; }; + const renderHarness = () => + React.createElement( + syncContext.Provider, + { value: system }, + React.createElement(syncRuntimeContext.Provider, { value: runtime }, React.createElement(Harness)), + ); + try { await act(async () => { - root.render(React.createElement(Provider, { value: system }, React.createElement(Harness))); + root.render(renderHarness()); }); expect(inactiveCount).toBe(0); enabled = true; await act(async () => { - root.render(React.createElement(Provider, { value: system }, React.createElement(Harness))); + root.render(renderHarness()); }); expect(activeCount).toBe(14); } finally { diff --git a/packages/ui/src/components/desktop/WindowsWindowControls.tsx b/packages/ui/src/components/desktop/WindowsWindowControls.tsx index 4d257e30..b74fdcfb 100644 --- a/packages/ui/src/components/desktop/WindowsWindowControls.tsx +++ b/packages/ui/src/components/desktop/WindowsWindowControls.tsx @@ -142,7 +142,9 @@ export const WindowsWindowControls = React.memo(function WindowsWindowControls({
@@ -207,7 +209,11 @@ export const WindowsWindowControls = React.memo(function WindowsWindowControls({ type="button" className={cn( buttonClassName, - 'hover:bg-[var(--status-error-background)] hover:text-[var(--status-error-foreground)]', + // Hover pairs the solid error red with its authored on-red + // foreground (the --destructive pairing). The error-background wash + // is a banner surface tint, not a glyph-button hover: against it the + // on-solid foreground is unreadable in both modes. + 'hover:bg-[var(--status-error)] hover:text-[var(--status-error-foreground)]', )} onClick={() => { void invokeDesktop('desktop_close_current_window'); }} title={t('header.windowControls.close')} diff --git a/packages/ui/src/components/layout/Header.tsx b/packages/ui/src/components/layout/Header.tsx index f8a3f331..7c1f3d3e 100644 --- a/packages/ui/src/components/layout/Header.tsx +++ b/packages/ui/src/components/layout/Header.tsx @@ -1356,6 +1356,14 @@ export const Header: React.FC = () => { return undefined; } + // Custom in-window controls (frameless Electron, right side) own the right + // edge: no inline padding, so the pr-0 class applies and the close button + // sits flush with the window corner per Windows conventions. Only the + // browser's native window-controls overlay reserves padding + right inset. + if (usesFramelessChrome && windowControlsSide === 'right') { + return undefined; + } + return { // Left inset is handled by the no-drag spacer (see renderDesktop); only // the right inset / titlebar height are owned by the window-controls overlay. @@ -1363,7 +1371,7 @@ export const Header: React.FC = () => { minHeight: 'max(3rem, var(--oc-wco-titlebar-height, 0px))', height: 'max(3rem, var(--oc-wco-titlebar-height, 0px))', }; - }, [isDesktopApp, isVSCode, usesFramelessChrome]); + }, [isDesktopApp, isVSCode, usesFramelessChrome, windowControlsSide]); const updateHeaderHeight = React.useCallback(() => { if (typeof document === 'undefined') {