From f1a857ee4deb647313f38105e239bb4510b5f63a Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 16 Apr 2026 15:37:54 -0700 Subject: [PATCH 1/2] Neutralize active pane colors when window loses focus. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds WindowFocusedContext so tab headers, selection overlay, and doors all respond to window blur — making it visually clear the window is inactive. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/src/components/Baseboard.tsx | 4 +++- lib/src/components/Door.tsx | 6 ++++-- lib/src/components/Pond.tsx | 10 ++++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/src/components/Baseboard.tsx b/lib/src/components/Baseboard.tsx index be78b467..655653de 100644 --- a/lib/src/components/Baseboard.tsx +++ b/lib/src/components/Baseboard.tsx @@ -1,7 +1,7 @@ import { useRef, useState, useMemo, useLayoutEffect, useContext, useSyncExternalStore, type ReactNode } from 'react'; import { CaretLeftIcon, CaretRightIcon } from '@phosphor-icons/react'; import { Door } from './Door'; -import { DoorElementsContext, type DetachedItem } from './Pond'; +import { DoorElementsContext, WindowFocusedContext, type DetachedItem } from './Pond'; import { DEFAULT_SESSION_UI_STATE, getSessionStateSnapshot, subscribeToSessionStateChanges } from '../lib/terminal-registry'; export interface BaseboardProps { @@ -13,6 +13,7 @@ export interface BaseboardProps { export function Baseboard({ items, activeId, onReattach, notice }: BaseboardProps) { const { elements: doorElements, bumpVersion } = useContext(DoorElementsContext); + const windowFocused = useContext(WindowFocusedContext); const sessionStates = useSyncExternalStore(subscribeToSessionStateChanges, getSessionStateSnapshot); const containerRef = useRef(null); const [containerWidth, setContainerWidth] = useState(0); @@ -176,6 +177,7 @@ export function Baseboard({ items, activeId, onReattach, notice }: BaseboardProp doorId={item.id} title={item.title} isActive={activeId === item.id} + windowFocused={windowFocused} status={sessionState.status} todo={sessionState.todo} diff --git a/lib/src/components/Door.tsx b/lib/src/components/Door.tsx index 9c2a8ad0..c45ac2b0 100644 --- a/lib/src/components/Door.tsx +++ b/lib/src/components/Door.tsx @@ -5,6 +5,7 @@ export interface DoorProps { doorId?: string; title: string; isActive?: boolean; + windowFocused?: boolean; status?: SessionStatus; todo?: TodoState; onClick?: () => void; @@ -14,6 +15,7 @@ export function Door({ doorId, title, isActive = false, + windowFocused = true, status = 'ALARM_DISABLED', todo = false, onClick, @@ -46,7 +48,7 @@ export function Door({ onClick={onClick} title={title} > - + {title} {(todo || alarmEnabled) && ( @@ -60,7 +62,7 @@ export function Door({ )} {alarmEnabled && ( - + {(status === 'MIGHT_BE_BUSY' || status === 'BUSY' || status === 'MIGHT_NEED_ATTENTION') && ( ({ export const RenamingIdContext = createContext(null); export const ZoomedContext = createContext(false); +export const WindowFocusedContext = createContext(true); const ARROW_OPPOSITES: Record = { ArrowLeft: 'ArrowRight', ArrowRight: 'ArrowLeft', @@ -487,11 +488,12 @@ export function TerminalPaneHeader({ api }: IDockviewPanelHeaderProps) { const selectedId = useContext(SelectedIdContext); const renamingId = useContext(RenamingIdContext); const zoomed = useContext(ZoomedContext); + const windowFocused = useContext(WindowFocusedContext); const sessionStates = useSyncExternalStore(subscribeToSessionStateChanges, getSessionStateSnapshot); const actions = useContext(PondActionsContext); const sessionState = sessionStates.get(api.id) ?? DEFAULT_SESSION_UI_STATE; const isSelected = selectedId === api.id; - const showSelectedHeader = mode === 'passthrough' && isSelected; + const showSelectedHeader = mode === 'passthrough' && isSelected && windowFocused; const isRenaming = renamingId === api.id; const tabRef = useRef(null); const suppressAlarmClickRef = useRef(false); @@ -812,7 +814,7 @@ function SelectionOverlay({ apiRef, selectedId, selectedType, mode }: { const { elements: panelElements, version: panelVersion } = useContext(PanelElementsContext); const { elements: doorElements, version: doorVersion } = useContext(DoorElementsContext); const selectionColor = useSelectionColor(); - const windowFocused = useWindowFocused(); + const windowFocused = useContext(WindowFocusedContext); const [rect, setRect] = useState<{ top: number; left: number; width: number; height: number } | null>(null); const isDoor = selectedType === 'door'; @@ -998,6 +1000,8 @@ export function Pond({ const [selectedId, setSelectedId] = useState(null); const [selectedType, setSelectedType] = useState<'pane' | 'door'>('pane'); + const windowFocused = useWindowFocused(); + // UI state const [confirmKill, setConfirmKill] = useState(null); useEffect(() => { if (!confirmKill) { clearTimeout(shakeTimerRef.current!); } }, [confirmKill]); @@ -1816,6 +1820,7 @@ export function Pond({ +
{/* Dockview */}
@@ -1844,6 +1849,7 @@ export function Pond({ )}
+ From 7914a0344908cd9a92ab2315da303e944746d9df Mon Sep 17 00:00:00 2001 From: nedtwigg Date: Thu, 16 Apr 2026 22:52:00 +0000 Subject: [PATCH 2/2] Codex review R1: add WindowFocused to context providers list in layout spec --- docs/specs/layout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specs/layout.md b/docs/specs/layout.md index f8410ea1..693fd7d9 100644 --- a/docs/specs/layout.md +++ b/docs/specs/layout.md @@ -22,7 +22,7 @@ The user can navigate between all elements using the mouse, or by entering `comm ``` Pond -├── Context providers (Mode, SelectedId, PondActions, PanelElements, DoorElements, RenamingId, Zoomed) +├── Context providers (Mode, SelectedId, PondActions, PanelElements, DoorElements, RenamingId, Zoomed, WindowFocused) │ └── div (h-screen, flex col) │ ├── Dockview wrapper (flex-1, 6px padding around edges) │ │ ├── DockviewReact (tiling layout engine, singleTabMode="fullwidth")