From 35a750eedd7b8b669dee3c9878ab157f1e4eb010 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Wed, 3 Jun 2026 21:44:30 -0500 Subject: [PATCH] feat(desktop): persistent needs-input indicator + icon button consolidation Replace the background-clarify toast (expired on alt-tab, easy to miss) with a persistent, glowing amber "needs input" dot on the session's sidebar row, driven off a new ClientSessionState.needsInput flag mirrored into a $attentionSessionIds store. The flag is set on clarify.request and cleared the moment the turn resumes (tool.complete) or ends. Also: redesign the clarify tool UI (borderless choices, pseudo-radio dots, right-aligned checkmark, arc border, tighter padding), make Button the single source of icon-button styling (4px radius, new icon-titlebar variant, titlebar buttons rendered polymorphically via asChild, Codicons throughout), put the file-tree refresh action first, and .trim() pasted composer text. --- apps/desktop/src/app/chat/composer/index.tsx | 10 ++- .../src/app/chat/sidebar/session-row.tsx | 52 ++++++++++-- apps/desktop/src/app/right-sidebar/index.tsx | 48 ++++++----- .../app/session/hooks/use-message-stream.ts | 23 +++--- .../session/hooks/use-session-state-cache.ts | 7 +- .../src/app/shell/titlebar-controls.tsx | 48 ++++++----- apps/desktop/src/app/shell/titlebar.ts | 6 +- apps/desktop/src/app/types.ts | 3 + .../components/assistant-ui/clarify-tool.tsx | 81 ++++++++----------- apps/desktop/src/components/ui/button.tsx | 9 ++- apps/desktop/src/lib/chat-runtime.ts | 3 +- apps/desktop/src/store/session.test.ts | 30 ++++++- apps/desktop/src/store/session.ts | 41 +++++++--- apps/desktop/src/styles.css | 31 +++++++ 14 files changed, 262 insertions(+), 130 deletions(-) diff --git a/apps/desktop/src/app/chat/composer/index.tsx b/apps/desktop/src/app/chat/composer/index.tsx index d19c0a253..6d1be3907 100644 --- a/apps/desktop/src/app/chat/composer/index.tsx +++ b/apps/desktop/src/app/chat/composer/index.tsx @@ -407,13 +407,19 @@ export function ChatBar({ return } - const pastedText = event.clipboardData.getData('text') + // Trim surrounding whitespace so a copy that dragged along leading/trailing + // blank lines (common when selecting from terminals, code blocks, web pages) + // doesn't dump multiline padding into the composer. Internal newlines are + // preserved — only the edges are cleaned up. + const pastedText = event.clipboardData.getData('text').trim() if (!pastedText) { + event.preventDefault() + return } - if (DATA_IMAGE_URL_RE.test(pastedText.trim())) { + if (DATA_IMAGE_URL_RE.test(pastedText)) { event.preventDefault() return diff --git a/apps/desktop/src/app/chat/sidebar/session-row.tsx b/apps/desktop/src/app/chat/sidebar/session-row.tsx index 81359a6f8..aaa623faa 100644 --- a/apps/desktop/src/app/chat/sidebar/session-row.tsx +++ b/apps/desktop/src/app/chat/sidebar/session-row.tsx @@ -1,3 +1,4 @@ +import { useStore } from '@nanostores/react' import type * as React from 'react' import { Button } from '@/components/ui/button' @@ -6,6 +7,7 @@ import type { SessionInfo } from '@/hermes' import { sessionTitle } from '@/lib/chat-runtime' import { triggerHaptic } from '@/lib/haptics' import { cn } from '@/lib/utils' +import { $attentionSessionIds } from '@/store/session' import { SessionActionsMenu, SessionContextMenu } from './session-actions-menu' @@ -61,6 +63,10 @@ export function SidebarSessionRow({ const title = sessionTitle(session) const age = formatAge(session.last_active || session.started_at) const handleLabel = `Reorder ${title}` + // Subscribe per-row (the leaf) instead of drilling a set through the list — + // the atom is tiny and rarely non-empty. True when a clarify prompt in this + // session is waiting on the user. + const needsInput = useStore($attentionSessionIds).includes(session.id) return ( - {isWorking &&