mirror of
https://github.com/block/goose.git
synced 2026-04-28 03:29:36 +00:00
polish: refine sidebar activity indicators, add placeholder token, and tidy search field (#8606)
Some checks are pending
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Goose 2 CI / Lint & Format (push) Waiting to run
Goose 2 CI / Unit Tests (push) Waiting to run
Goose 2 CI / Desktop Build & E2E (push) Waiting to run
Goose 2 CI / Rust Lint (push) Waiting to run
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Some checks are pending
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Goose 2 CI / Lint & Format (push) Waiting to run
Goose 2 CI / Unit Tests (push) Waiting to run
Goose 2 CI / Desktop Build & E2E (push) Waiting to run
Goose 2 CI / Rust Lint (push) Waiting to run
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
This commit is contained in:
parent
fdd8ada032
commit
bd14186214
9 changed files with 95 additions and 57 deletions
|
|
@ -315,7 +315,7 @@ export function Sidebar({
|
|||
collapsed ? "justify-center" : "justify-between",
|
||||
)}
|
||||
>
|
||||
<GooseIcon className="text-muted-foreground" />
|
||||
<GooseIcon className="text-foreground" />
|
||||
{!collapsed && (
|
||||
<Button
|
||||
type="button"
|
||||
|
|
@ -367,49 +367,36 @@ export function Sidebar({
|
|||
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center w-full rounded-md transition-all duration-300 ease-out",
|
||||
"mb-4 flex items-center w-full rounded-md transition-all duration-300 ease-out",
|
||||
collapsed
|
||||
? "justify-center p-3 text-muted-foreground"
|
||||
: "gap-2 border border-border px-2.5 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-transparent",
|
||||
)}
|
||||
>
|
||||
<Search className="size-3.5 flex-shrink-0" />
|
||||
<Search className="size-3.5 flex-shrink-0 text-placeholder" />
|
||||
{!collapsed && (
|
||||
<>
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type="text"
|
||||
enterKeyHint="search"
|
||||
value={sidebarSearch.query}
|
||||
onChange={(e) => sidebarSearch.setQuery(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
void sidebarSearch.search();
|
||||
}
|
||||
}}
|
||||
placeholder={t("search.placeholder")}
|
||||
className={cn(
|
||||
"focus-override appearance-none bg-transparent border-none text-xs flex-1 min-w-0 placeholder:text-muted-foreground outline-none focus-visible:ring-0 focus-visible:ring-offset-0",
|
||||
labelTransition,
|
||||
labelVisible
|
||||
? "opacity-100 w-auto"
|
||||
: "opacity-0 w-0 overflow-hidden",
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<kbd
|
||||
className={cn(
|
||||
"text-[10px] text-muted-foreground px-1 py-0.5 rounded font-mono flex-shrink-0",
|
||||
labelTransition,
|
||||
labelVisible
|
||||
? "opacity-100 w-auto"
|
||||
: "opacity-0 w-0 overflow-hidden px-0",
|
||||
)}
|
||||
>
|
||||
⌘K
|
||||
</kbd>
|
||||
</>
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type="text"
|
||||
enterKeyHint="search"
|
||||
value={sidebarSearch.query}
|
||||
onChange={(e) => sidebarSearch.setQuery(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
void sidebarSearch.search();
|
||||
}
|
||||
}}
|
||||
placeholder={t("search.placeholder")}
|
||||
className={cn(
|
||||
"focus-override appearance-none bg-transparent border-none text-xs flex-1 min-w-0 placeholder:text-placeholder outline-none focus-visible:ring-0 focus-visible:ring-offset-0",
|
||||
labelTransition,
|
||||
labelVisible
|
||||
? "opacity-100 w-auto"
|
||||
: "opacity-0 w-0 overflow-hidden",
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ export function SidebarChatRow({
|
|||
t("common:session.defaultTitle"),
|
||||
);
|
||||
const [draftTitle, setDraftTitle] = useState(editableTitle);
|
||||
const showActivityIndicator = isRunning || hasUnread;
|
||||
|
||||
useEffect(() => {
|
||||
setDraftTitle(editableTitle);
|
||||
|
|
@ -186,10 +187,17 @@ export function SidebarChatRow({
|
|||
isActive ? ACTIVE_CHAT_ROW_CLASS : INACTIVE_CHAT_ROW_CLASS,
|
||||
)}
|
||||
>
|
||||
{showActivityIndicator && (
|
||||
<span className="flex h-3 w-3 shrink-0 items-center justify-center">
|
||||
<SessionActivityIndicator
|
||||
isRunning={isRunning}
|
||||
hasUnread={hasUnread}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
<span className="flex-1 min-w-0 truncate text-left">
|
||||
{displayTitle}
|
||||
</span>
|
||||
<SessionActivityIndicator isRunning={isRunning} hasUnread={hasUnread} />
|
||||
</Button>
|
||||
|
||||
<DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
|
||||
|
|
|
|||
|
|
@ -108,6 +108,37 @@ describe("SidebarChatRow", () => {
|
|||
expect(screen.getByLabelText(/unread messages/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not reserve activity space by default when idle", () => {
|
||||
const { container } = render(
|
||||
<SidebarChatRow id="session-1" title="Idle Chat" isActive={false} />,
|
||||
);
|
||||
|
||||
expect(
|
||||
container.querySelector(".h-3.w-3.shrink-0.items-center.justify-center"),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("reserves activity space only once activity exists", () => {
|
||||
const { container, rerender } = render(
|
||||
<SidebarChatRow id="session-1" title="Recent Chat" isActive={false} />,
|
||||
);
|
||||
|
||||
expect(
|
||||
container.querySelector(".h-3.w-3.shrink-0.items-center.justify-center"),
|
||||
).toBeNull();
|
||||
|
||||
rerender(
|
||||
<SidebarChatRow
|
||||
id="session-1"
|
||||
title="Recent Chat"
|
||||
isActive={false}
|
||||
hasUnread
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText(/unread messages/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the localized default title in rename mode without persisting it", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onRename = vi.fn();
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@
|
|||
--text-success: var(--color-green-300);
|
||||
--text-warning: var(--color-yellow-200);
|
||||
--text-info: var(--color-blue-200);
|
||||
--text-placeholder: var(--color-gray-400);
|
||||
|
||||
--ring: color-mix(in srgb, var(--border-strong) 20%, transparent);
|
||||
|
||||
|
|
@ -304,6 +305,7 @@
|
|||
--text-success: var(--color-green-100);
|
||||
--text-warning: var(--color-yellow-100);
|
||||
--text-info: var(--color-blue-100);
|
||||
--text-placeholder: var(--color-gray-600);
|
||||
|
||||
--ring: color-mix(in srgb, var(--border-strong) 20%, transparent);
|
||||
|
||||
|
|
@ -356,6 +358,7 @@
|
|||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-placeholder: var(--text-placeholder);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
|
|
@ -407,6 +410,7 @@
|
|||
--color-text-success: var(--text-success);
|
||||
--color-text-warning: var(--text-warning);
|
||||
--color-text-info: var(--text-info);
|
||||
--color-text-placeholder: var(--text-placeholder);
|
||||
|
||||
/* alpha variants */
|
||||
--color-dark-10: var(--dark-10);
|
||||
|
|
|
|||
|
|
@ -3,25 +3,22 @@ import { describe, expect, it } from "vitest";
|
|||
import { SessionActivityIndicator } from "./SessionActivityIndicator";
|
||||
|
||||
describe("SessionActivityIndicator", () => {
|
||||
it("renders a brand-colored inline spinner for running sessions", () => {
|
||||
it("renders an inline spinner for running sessions", () => {
|
||||
render(<SessionActivityIndicator isRunning />);
|
||||
|
||||
expect(screen.getByLabelText(/chat active/i)).toHaveClass("text-brand");
|
||||
expect(screen.getByLabelText(/chat active/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders a brand-colored inline dot for unread sessions", () => {
|
||||
it("renders an inline dot for unread sessions", () => {
|
||||
render(<SessionActivityIndicator hasUnread />);
|
||||
|
||||
expect(screen.getByLabelText(/unread messages/i)).toHaveClass("bg-brand");
|
||||
expect(screen.getByLabelText(/unread messages/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders an overlay spinner variant for running sessions", () => {
|
||||
const { container } = render(
|
||||
<SessionActivityIndicator isRunning variant="overlay" />,
|
||||
);
|
||||
render(<SessionActivityIndicator isRunning variant="overlay" />);
|
||||
|
||||
expect(screen.getByLabelText(/chat active/i)).toBeInTheDocument();
|
||||
expect(container.querySelector(".text-brand")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders nothing when the session is idle and read", () => {
|
||||
|
|
|
|||
|
|
@ -21,24 +21,32 @@ export function SessionActivityIndicator({
|
|||
role="status"
|
||||
aria-label="Chat active"
|
||||
className={cn(
|
||||
"absolute -right-1 -top-1 flex h-3.5 w-3.5 items-center justify-center rounded-full border border-background bg-background shadow-sm",
|
||||
"absolute -right-1 -top-1 flex h-3.5 w-3.5 items-center justify-center rounded-full border border-background bg-background shadow-sm transition-opacity duration-200 ease-out animate-in fade-in-0",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Loader2
|
||||
aria-hidden="true"
|
||||
className="h-2.5 w-2.5 animate-spin text-brand"
|
||||
className="h-2.5 w-2.5 animate-spin text-text-info"
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Loader2
|
||||
<span
|
||||
role="status"
|
||||
aria-label="Chat active"
|
||||
className={cn("h-3 w-3 shrink-0 animate-spin text-brand", className)}
|
||||
/>
|
||||
className={cn(
|
||||
"inline-flex h-3 w-3 shrink-0 items-center justify-center animate-in fade-in-0 duration-200 ease-out",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Loader2
|
||||
aria-hidden="true"
|
||||
className="h-3 w-3 animate-spin text-text-info"
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +60,7 @@ export function SessionActivityIndicator({
|
|||
role="status"
|
||||
aria-label="Unread messages"
|
||||
className={cn(
|
||||
"absolute -right-0.5 -top-0.5 h-2.5 w-2.5 shrink-0 rounded-full border border-background bg-brand",
|
||||
"absolute -right-0.5 -top-0.5 h-2 w-2 shrink-0 rounded-full border border-background bg-background-info transition-opacity duration-200 ease-out animate-in fade-in-0",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
|
|
@ -63,7 +71,10 @@ export function SessionActivityIndicator({
|
|||
<span
|
||||
role="status"
|
||||
aria-label="Unread messages"
|
||||
className={cn("h-2 w-2 shrink-0 rounded-full bg-brand", className)}
|
||||
className={cn(
|
||||
"h-1.5 w-1.5 shrink-0 rounded-full bg-background-info transition-opacity duration-200 ease-out animate-in fade-in-0",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ function CommandInput({
|
|||
<CommandPrimitive.Input
|
||||
data-slot="command-input"
|
||||
className={cn(
|
||||
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"placeholder:text-placeholder flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { cn } from "@/shared/lib/cn";
|
|||
|
||||
const variantStyles = {
|
||||
default: [
|
||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-input hover:border-border-input-hover focus-visible:border-ring focus-visible:ring-0 focus-visible:ring-offset-0 flex h-9 w-full min-w-0 rounded-input border bg-transparent px-3 py-1 text-base transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"file:text-foreground placeholder:text-placeholder selection:bg-primary selection:text-primary-foreground border-input hover:border-border-input-hover focus-visible:border-ring focus-visible:ring-0 focus-visible:ring-offset-0 flex h-9 w-full min-w-0 rounded-input border bg-transparent px-3 py-1 text-base transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
],
|
||||
ghost: [
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|||
<textarea
|
||||
data-slot="textarea"
|
||||
className={cn(
|
||||
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-0 focus-visible:ring-offset-0 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"border-input placeholder:text-placeholder focus-visible:border-ring focus-visible:ring-0 focus-visible:ring-offset-0 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue