diff --git a/apps/desktop/src/components/layout/SidebarShell.tsx b/apps/desktop/src/components/layout/SidebarShell.tsx index 45fdd484..96806dce 100644 --- a/apps/desktop/src/components/layout/SidebarShell.tsx +++ b/apps/desktop/src/components/layout/SidebarShell.tsx @@ -137,7 +137,7 @@ function SidebarTabButton({ ? "0 1px 2px rgb(0 0 0 / 0.12)" : "none", transition: - "background-color 120ms ease, color 120ms ease, border-color 120ms ease, transform 120ms ease", + "background-color 140ms ease-out, color 140ms ease-out, border-color 140ms ease-out, transform 140ms cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 140ms ease-out", }} > diff --git a/apps/desktop/src/features/ai/AgentsSidebarPanel.tsx b/apps/desktop/src/features/ai/AgentsSidebarPanel.tsx index 5c798f01..bf5a60f8 100644 --- a/apps/desktop/src/features/ai/AgentsSidebarPanel.tsx +++ b/apps/desktop/src/features/ai/AgentsSidebarPanel.tsx @@ -741,12 +741,13 @@ export function AgentsSidebarPanel() { }} title="New chat" aria-label="New chat" - className="flex h-5 w-5 items-center justify-center rounded" + className="ub-chrome-btn flex h-5 w-5 cursor-pointer items-center justify-center rounded" style={{ width: metrics.actionButtonSize, height: metrics.actionButtonSize, color: "var(--text-secondary)", background: "transparent", + border: "1px solid transparent", }} > openChatHistoryInWorkspace()} title="Open chat history" - className="rounded px-1.5 py-0.5 text-[10.5px]" + className="ub-chrome-btn cursor-pointer rounded px-1.5 py-0.5 text-[10.5px]" style={{ color: "var(--text-secondary)", background: "transparent", + border: "1px solid transparent", fontSize: metrics.summaryFontSize, }} > diff --git a/apps/desktop/src/features/ai/components/AIChatAgentControls.tsx b/apps/desktop/src/features/ai/components/AIChatAgentControls.tsx index 5415c037..d4c5d4d2 100644 --- a/apps/desktop/src/features/ai/components/AIChatAgentControls.tsx +++ b/apps/desktop/src/features/ai/components/AIChatAgentControls.tsx @@ -144,24 +144,13 @@ function DropdownField({ rememberFocusedElement(); setOpen(true); }} - className="flex items-center gap-1 rounded-md px-2 py-1 text-xs" + className="nw-control-trigger flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-xs" + data-open={open ? "true" : undefined} style={{ color: "var(--text-secondary)", backgroundColor: "transparent", border: "none", opacity: isDisabled ? 0.45 : 1, - transition: "background-color 100ms ease, color 100ms ease", - }} - onMouseEnter={(e) => { - if (isDisabled) return; - e.currentTarget.style.backgroundColor = - "color-mix(in srgb, var(--bg-tertiary) 80%, transparent)"; - e.currentTarget.style.color = "var(--text-primary)"; - }} - onMouseLeave={(e) => { - if (isDisabled) return; - e.currentTarget.style.backgroundColor = "transparent"; - e.currentTarget.style.color = "var(--text-secondary)"; }} title={label} disabled={isDisabled} diff --git a/apps/desktop/src/features/ai/components/AIChatComposer.tsx b/apps/desktop/src/features/ai/components/AIChatComposer.tsx index 93b616e9..63240389 100644 --- a/apps/desktop/src/features/ai/components/AIChatComposer.tsx +++ b/apps/desktop/src/features/ai/components/AIChatComposer.tsx @@ -1082,6 +1082,12 @@ export function AIChatComposer({ const stopTransitionLabel = hasPendingSubmitAfterStop ? "Sending next message after stop..." : "Stopping previous run..."; + const stopButtonVisible = isStreaming || isStopping; + const stopButtonOpacity = !stopButtonVisible + ? 0 + : disabled || isStopping + ? 0.4 + : 1; const closeMentionPicker = () => setMentionState(EMPTY_MENTION_STATE); const closeSlashPicker = () => setSlashState(EMPTY_SLASH_STATE); @@ -1672,8 +1678,12 @@ export function AIChatComposer({ stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" + strokeLinejoin="round" > - + {/* Collapse: inner brackets at (5,5) and (9,9) + with diagonals out to the outer corners — reads + as arrows pulling inward toward the centre. */} + ) : ( + {/* Expand: outer brackets at the corners with + diagonals pushing outward — arrows reaching + toward the corners. */} )} @@ -1945,24 +1959,91 @@ export function AIChatComposer({ minHeight: expanded ? 42 : undefined, }} > -
- {isStopTransitionActive ? ( -
- {stopTransitionLabel} -
- ) : null} - {footer} +
+
+ {footer} +
+
+ {stopTransitionLabel} +
+ {/* Stop is rendered unconditionally and placed before + Send in DOM order so Send stays anchored to the right + edge regardless of streaming state. Visibility toggles + via opacity + pointer-events for a smooth fade with no + layout shift. */} + - {(isStreaming || isStopping) && ( - - )}
{ + setComposerExpanded(false); void chatActions.sendMessage(sessionId); }} onStop={() => { diff --git a/apps/desktop/src/features/editor/EditorPaneBar.tsx b/apps/desktop/src/features/editor/EditorPaneBar.tsx index 184d79af..54161b03 100644 --- a/apps/desktop/src/features/editor/EditorPaneBar.tsx +++ b/apps/desktop/src/features/editor/EditorPaneBar.tsx @@ -788,6 +788,9 @@ export function EditorPaneBar({ paneId, isFocused }: EditorPaneBarProps) { width: 20, height: 20, color: "var(--text-secondary)", + // Match UnifiedBar tab close: sit closer to the + // right edge of the tab. + marginRight: -6, }} > !disabled && onChange(!value)} + className="nw-settings-toggle" style={{ width: 36, height: 20, @@ -93,7 +95,6 @@ function Toggle({ backgroundColor: value ? "var(--accent)" : "var(--bg-tertiary)", position: "relative", flexShrink: 0, - transition: "background-color 150ms", opacity: disabled ? 0.4 : 1, }} > @@ -139,6 +140,8 @@ function SegmentedControl({ @@ -419,18 +416,23 @@ function NumberStepper({ }} > {checked ? "✓" : ""} @@ -126,14 +122,14 @@ export function VaultSwitcher({ style={{ position: "absolute", bottom: "100%", - left: 0, - right: 0, + left: 8, + right: 8, marginBottom: 4, zIndex: 9999, borderRadius: 8, backgroundColor: "var(--bg-secondary)", border: "1px solid var(--border)", - boxShadow: "0 4px 20px rgba(0,0,0,0.3)", + boxShadow: "0 8px 24px rgba(0,0,0,0.35)", padding: 4, }} > @@ -301,15 +297,28 @@ export function VaultSwitcher({ payload: { path: vaultPath }, }); }} - className="w-full flex items-center gap-2 px-3 py-3 text-xs" - style={{ color: "var(--text-secondary)" }} + data-open={isOpen ? "true" : undefined} + className="nw-vault-trigger flex w-full cursor-pointer items-center gap-2 text-xs" + style={{ + margin: "4px 8px", + width: "calc(100% - 16px)", + padding: "4px 8px", + borderRadius: 7, + border: "1px solid color-mix(in srgb, var(--border) 70%, transparent)", + background: "color-mix(in srgb, var(--bg-tertiary) 38%, transparent)", + color: "var(--text-secondary)", + }} >