diff --git a/docs/docs/config.mdx b/docs/docs/config.mdx index aad9e7d8c6..ff1a87db80 100644 --- a/docs/docs/config.mdx +++ b/docs/docs/config.mdx @@ -67,6 +67,7 @@ wsh editconfig | term:macoptionismeta | bool | on macOS, treat the Option key as Meta key for terminal keybindings (default false) | | term:bellsound | bool | when enabled, plays the system beep sound when the terminal bell (BEL character) is received (default false) | | term:bellindicator | bool | when enabled, shows a visual indicator in the tab when the terminal bell is received (default false) | +| term:exitindicator | bool | when enabled, shows a colored tab indicator when a process exits — green for success, red for error (default false) | | term:durable | bool | makes remote terminal sessions durable across network disconnects (defaults to true) | | editor:minimapenabled | bool | set to false to disable editor minimap | | editor:stickyscrollenabled | bool | enables monaco editor's stickyScroll feature (pinning headers of current context, e.g. class names, method names, etc.), defaults to false | @@ -134,6 +135,7 @@ For reference, this is the current default configuration (v0.11.5): "telemetry:enabled": true, "term:bellsound": false, "term:bellindicator": false, + "term:exitindicator": false, "term:copyonselect": true, "term:durable": true, "waveai:showcloudmodes": true, diff --git a/frontend/app/tab/tab.scss b/frontend/app/tab/tab.scss index 3739752eee..211f996aa3 100644 --- a/frontend/app/tab/tab.scss +++ b/frontend/app/tab/tab.scss @@ -1,6 +1,28 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +// Animate a shared breathing phase on :root so all indicator tabs pulse in sync. +// Uses CSS @property for an animatable custom property (Chromium 78+). +@property --breathe-phase { + syntax: ""; + initial-value: 0; + inherits: true; +} + +:root { + animation: indicatorBreathePhase 16s ease-in-out infinite; +} + +@keyframes indicatorBreathePhase { + 0%, + 100% { + --breathe-phase: 0; + } + 50% { + --breathe-phase: 1; + } +} + .tab { position: absolute; width: 130px; @@ -96,6 +118,24 @@ transition: none !important; } + &.has-indicator { + .tab-inner { + background: rgb(from var(--tab-indicator-color) r g b / 0.15); + } + &.active .tab-inner { + background: rgb(from var(--tab-indicator-color) r g b / 0.2); + } + } + + &.indicator-breathing { + .tab-inner { + background: rgb(from var(--tab-indicator-color) r g b / calc(0.08 + var(--breathe-phase) * 0.14)); + } + &.active .tab-inner { + background: rgb(from var(--tab-indicator-color) r g b / calc(0.12 + var(--breathe-phase) * 0.14)); + } + } + .wave-button { position: absolute; top: 50%; @@ -129,6 +169,18 @@ body:not(.nohover) .tab.dragging { border-color: transparent; background: rgb(from var(--main-text-color) r g b / 0.1); } + &.has-indicator .tab-inner { + background: rgb(from var(--tab-indicator-color) r g b / 0.15); + } + &.has-indicator.active .tab-inner { + background: rgb(from var(--tab-indicator-color) r g b / 0.2); + } + &.indicator-breathing .tab-inner { + background: rgb(from var(--tab-indicator-color) r g b / calc(0.08 + var(--breathe-phase) * 0.14)); + } + &.indicator-breathing.active .tab-inner { + background: rgb(from var(--tab-indicator-color) r g b / calc(0.12 + var(--breathe-phase) * 0.14)); + } .close { visibility: visible; &:hover { diff --git a/frontend/app/tab/tab.tsx b/frontend/app/tab/tab.tsx index 9a3d0f9925..814cae3d31 100644 --- a/frontend/app/tab/tab.tsx +++ b/frontend/app/tab/tab.tsx @@ -224,7 +224,14 @@ const Tab = memo( dragging: isDragging, "before-active": isBeforeActive, "new-tab": isNew, + "has-indicator": indicator != null, + "indicator-breathing": indicator != null && indicator.icon === "none", })} + style={ + indicator != null + ? ({ "--tab-indicator-color": indicator.color || "#f59e0b" } as React.CSSProperties) + : undefined + } onMouseDown={onDragStart} onClick={handleTabClick} onContextMenu={handleContextMenu} @@ -242,7 +249,7 @@ const Tab = memo( > {tabData?.name} - {indicator && ( + {indicator && indicator.icon !== "none" && (