From 4053eaf4e1dd6c740ee42040d0f32faae3d5cdf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Gurruchaga?= Date: Sun, 17 May 2026 05:51:38 -0400 Subject: [PATCH 01/10] Add hover and active feedback to agents sidebar toolbar buttons --- apps/desktop/src/features/ai/AgentsSidebarPanel.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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, }} > From e78b4af60737f3088ba84615377a4eb57377a79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Gurruchaga?= Date: Sun, 17 May 2026 05:57:30 -0400 Subject: [PATCH 02/10] Make sidebar tabs feel tactile with lift, shadow and press states --- .../src/components/layout/SidebarShell.tsx | 2 +- apps/desktop/src/index.css | 26 ++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) 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/index.css b/apps/desktop/src/index.css index e6db7cce..256c9994 100644 --- a/apps/desktop/src/index.css +++ b/apps/desktop/src/index.css @@ -865,10 +865,10 @@ body.dragging-tab * { ) !important; } -/* Sidebar shell tabs. Mirrors Comando's `.sidebar-git-scope-trigger:hover` - feel — subtle background/border shift plus a 1px lift so the pill - "jumps" forward under the cursor. Works for both inactive and active - tabs; active tabs keep their accent-aware border intact. */ +/* Sidebar shell tabs. A small physical-button feel: lift + soft shadow on + hover, then a snappy press-down with inset shadow on :active so the pill + tangibly depresses under the cursor. Active tabs share the same press + behaviour while keeping their accent-aware border intact. */ .ub-sidebar-tab:hover:not([data-active]) { background-color: color-mix( in srgb, @@ -882,9 +882,27 @@ body.dragging-tab * { ); color: var(--text-primary); transform: translateY(-1px); + box-shadow: + 0 2px 6px color-mix(in srgb, black 18%, transparent), + inset 0 1px 0 color-mix(in srgb, var(--text-primary) 8%, transparent); } .ub-sidebar-tab[data-active]:hover { transform: translateY(-1px); + box-shadow: + 0 2px 6px color-mix(in srgb, black 22%, transparent), + 0 1px 2px color-mix(in srgb, black 10%, transparent); +} +.ub-sidebar-tab:active:not(:disabled), +.ub-sidebar-tab[data-active]:active:not(:disabled) { + transform: translateY(0); + box-shadow: + inset 0 1px 2px color-mix(in srgb, black 28%, transparent), + inset 0 0 0 1px color-mix(in srgb, black 6%, transparent); + transition-duration: 60ms; +} +.ub-sidebar-tab:focus-visible { + outline: 2px solid color-mix(in srgb, var(--accent) 60%, transparent); + outline-offset: 1px; } .ub-nav-btn { From 2f7a79f24a57ff85b62d077d3426b88b1ae4ca4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Gurruchaga?= Date: Sun, 17 May 2026 06:00:52 -0400 Subject: [PATCH 03/10] Make composer send and stop buttons sink on press --- .../features/ai/components/AIChatComposer.tsx | 10 ++++++---- apps/desktop/src/index.css | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/features/ai/components/AIChatComposer.tsx b/apps/desktop/src/features/ai/components/AIChatComposer.tsx index 93b616e9..b4b0d14a 100644 --- a/apps/desktop/src/features/ai/components/AIChatComposer.tsx +++ b/apps/desktop/src/features/ai/components/AIChatComposer.tsx @@ -1962,7 +1962,7 @@ export function AIChatComposer({ type="button" onClick={onSubmit} disabled={!canSubmit} - className="flex shrink-0 items-center justify-center rounded-full" + className="nw-composer-action flex shrink-0 cursor-pointer items-center justify-center rounded-full" style={{ width: 28, height: 28, @@ -1972,7 +1972,8 @@ export function AIChatComposer({ : "var(--accent)", border: "none", opacity: canSubmit ? 1 : 0.4, - transition: "all 0.15s ease", + transition: + "transform 120ms cubic-bezier(0.34, 1.56, 0.64, 1), filter 120ms ease-out, box-shadow 120ms ease-out, background-color 150ms ease, color 150ms ease, opacity 150ms ease", }} aria-label={ hasPendingSubmitAfterStop @@ -2007,7 +2008,7 @@ export function AIChatComposer({ type="button" onClick={onStop} disabled={disabled || isStopping} - className="flex shrink-0 items-center justify-center rounded-full" + className="nw-composer-action flex shrink-0 cursor-pointer items-center justify-center rounded-full" style={{ width: 28, height: 28, @@ -2015,7 +2016,8 @@ export function AIChatComposer({ backgroundColor: "#b91c1c", border: "none", opacity: disabled || isStopping ? 0.4 : 1, - transition: "all 0.15s ease", + transition: + "transform 120ms cubic-bezier(0.34, 1.56, 0.64, 1), filter 120ms ease-out, box-shadow 120ms ease-out, background-color 150ms ease, opacity 150ms ease", }} aria-label={isStopping ? "Stopping" : "Stop"} title={isStopping ? "Stopping" : "Stop"} diff --git a/apps/desktop/src/index.css b/apps/desktop/src/index.css index 256c9994..3a8777e6 100644 --- a/apps/desktop/src/index.css +++ b/apps/desktop/src/index.css @@ -932,6 +932,26 @@ body.dragging-tab * { filter: brightness(0.95); } +/* Composer send/stop pill buttons. The press should feel like a physical + button depressing: shrink slightly, dim, and gain an inset shadow that + replaces the lifted drop shadow. */ +.nw-composer-action { + transform: translateZ(0); +} +.nw-composer-action:hover:not(:disabled) { + filter: brightness(1.08); + box-shadow: 0 2px 6px color-mix(in srgb, black 24%, transparent); +} +.nw-composer-action:active:not(:disabled) { + transform: scale(0.9); + filter: brightness(0.9); + box-shadow: inset 0 2px 4px color-mix(in srgb, black 36%, transparent); +} +.nw-composer-action:focus-visible { + outline: 2px solid color-mix(in srgb, var(--accent) 60%, transparent); + outline-offset: 2px; +} + .ub-tab:not([data-active="true"]):hover { background-color: color-mix( in srgb, From 231f28c65687d366afe4bb4978ecd2acb206c900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Gurruchaga?= Date: Sun, 17 May 2026 06:02:33 -0400 Subject: [PATCH 04/10] Add tactile press and open state to composer agent control dropdowns --- .../ai/components/AIChatAgentControls.tsx | 15 +------ apps/desktop/src/index.css | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 13 deletions(-) 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/index.css b/apps/desktop/src/index.css index 3a8777e6..9e976d35 100644 --- a/apps/desktop/src/index.css +++ b/apps/desktop/src/index.css @@ -932,6 +932,49 @@ body.dragging-tab * { filter: brightness(0.95); } +/* Composer agent-control dropdown triggers (Approval preset, Model, + Reasoning effort, …). Hover lifts background subtly, the open state + stays pressed-in until the menu closes, and a press scales down with + an inset shadow for a tactile click. */ +.nw-control-trigger { + transition: + background-color 120ms ease-out, + color 120ms ease-out, + box-shadow 120ms ease-out, + transform 120ms cubic-bezier(0.34, 1.56, 0.64, 1); +} +.nw-control-trigger:hover:not(:disabled) { + background-color: color-mix( + in srgb, + var(--bg-tertiary) 82%, + transparent + ) !important; + color: var(--text-primary) !important; +} +.nw-control-trigger[data-open="true"]:not(:disabled) { + background-color: color-mix( + in srgb, + var(--bg-tertiary) 94%, + transparent + ) !important; + color: var(--text-primary) !important; + box-shadow: inset 0 1px 2px color-mix(in srgb, black 18%, transparent); +} +.nw-control-trigger:active:not(:disabled) { + transform: scale(0.96); + background-color: color-mix( + in srgb, + var(--bg-tertiary) 96%, + transparent + ) !important; + box-shadow: inset 0 1px 2px color-mix(in srgb, black 28%, transparent); + transition-duration: 60ms; +} +.nw-control-trigger:focus-visible { + outline: 2px solid color-mix(in srgb, var(--accent) 60%, transparent); + outline-offset: 1px; +} + /* Composer send/stop pill buttons. The press should feel like a physical button depressing: shrink slightly, dim, and gain an inset shadow that replaces the lifted drop shadow. */ From df583dc15a17d82fec66acfd79188153e675468b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Gurruchaga?= Date: Sun, 17 May 2026 06:13:26 -0400 Subject: [PATCH 05/10] Redesign vault switcher as inset card with tactile interactivity --- .../src/features/vault/VaultSwitcher.tsx | 39 ++++++---- apps/desktop/src/index.css | 77 +++++++++++++++++++ 2 files changed, 102 insertions(+), 14 deletions(-) diff --git a/apps/desktop/src/features/vault/VaultSwitcher.tsx b/apps/desktop/src/features/vault/VaultSwitcher.tsx index a3d080cd..a4d4cd99 100644 --- a/apps/desktop/src/features/vault/VaultSwitcher.tsx +++ b/apps/desktop/src/features/vault/VaultSwitcher.tsx @@ -86,16 +86,12 @@ export function VaultSwitcher({ {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)", + }} >
- {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) && ( - - )}
Date: Sun, 17 May 2026 06:50:01 -0400 Subject: [PATCH 08/10] Add tactile hover, press and focus states to settings primitives --- .../src/features/settings/SettingsPanel.tsx | 35 ++-- apps/desktop/src/index.css | 164 ++++++++++++++++++ 2 files changed, 186 insertions(+), 13 deletions(-) diff --git a/apps/desktop/src/features/settings/SettingsPanel.tsx b/apps/desktop/src/features/settings/SettingsPanel.tsx index 3ec32482..c38d86d6 100644 --- a/apps/desktop/src/features/settings/SettingsPanel.tsx +++ b/apps/desktop/src/features/settings/SettingsPanel.tsx @@ -83,7 +83,9 @@ function Toggle({ @@ -419,18 +416,23 @@ function NumberStepper({ }} >