Queue items (edit, send now, delete)

This commit is contained in:
Mark 2026-02-19 21:58:10 -08:00
parent a0ba55feec
commit fcbec6c4d6
7 changed files with 279 additions and 162 deletions

View File

@ -691,7 +691,7 @@ export function ChainOfThought({ parts, isStreaming }: { parts: ChainPart[]; isS
className="flex items-start gap-2.5 py-1.5"
>
<div
className="relative z-10 flex-shrink-0 w-5 h-5 mt-0.5 flex items-center justify-center rounded-full"
className="relative z-10 flex-shrink-0 w-[18px] h-[18px] flex items-center justify-center rounded-full"
style={{
background: "var(--color-bg)",
}}
@ -800,20 +800,12 @@ function FetchGroup({ items }: { items: ToolPart[] }) {
return (
<div className="flex items-start gap-2.5 py-1.5">
<div
className="relative z-10 flex-shrink-0 w-5 h-5 mt-0.5 flex items-center justify-center rounded-full"
className="relative z-10 flex-shrink-0 w-[18px] h-[18px] flex items-center justify-center rounded-full"
style={{ background: "var(--color-bg)" }}
>
{anyRunning ? (
<span
className="w-4 h-4 border-[1.5px] rounded-full animate-spin"
style={{
borderColor: "var(--color-border-strong)",
borderTopColor: "var(--color-accent)",
}}
/>
) : (
<span className={anyRunning ? "animate-pulse" : ""}>
<StepIcon kind="fetch" />
)}
</span>
</div>
<div className="flex-1 min-w-0">
<div
@ -824,7 +816,7 @@ function FetchGroup({ items }: { items: ToolPart[] }) {
: "var(--color-text-secondary)",
}}
>
<span>
<span className={anyRunning ? "animate-pulse" : ""}>
{anyRunning
? `Fetching ${items.length} sources...`
: `Fetched ${items.length} sources`}
@ -984,18 +976,10 @@ function MediaGroup({
return (
<div className="flex items-start gap-2.5 py-1.5">
<div
className="relative z-10 flex-shrink-0 w-5 h-5 mt-0.5 flex items-center justify-center rounded-full"
className="relative z-10 flex-shrink-0 w-[18px] h-[18px] flex items-center justify-center rounded-full"
style={{ background: "var(--color-bg)" }}
>
{anyRunning ? (
<span
className="w-4 h-4 border-[1.5px] rounded-full animate-spin"
style={{
borderColor: "var(--color-border-strong)",
borderTopColor: "var(--color-accent)",
}}
/>
) : (
<span className={anyRunning ? "animate-pulse" : ""}>
<StepIcon
kind={
mediaKind === "image"
@ -1003,11 +987,11 @@ function MediaGroup({
: "read"
}
/>
)}
</span>
</div>
<div className="flex-1 min-w-0">
<div
className="text-[13px] leading-snug mb-1.5"
className={`text-[13px] leading-snug mb-1.5${anyRunning ? " animate-pulse" : ""}`}
style={{
color: anyRunning
? "var(--color-text)"
@ -1038,15 +1022,9 @@ function MediaGroup({
border: "1px solid var(--color-border)",
}}
>
<span
className="w-4 h-4 border-[1.5px] rounded-full animate-spin"
style={{
borderColor:
"var(--color-border-strong)",
borderTopColor:
"var(--color-accent)",
}}
/>
<span className="animate-pulse" style={{ color: "var(--color-text-muted)" }}>
<StepIcon kind="image" />
</span>
</div>
)}
{hasMore && (
@ -1259,27 +1237,21 @@ function ToolStep({
return (
<div className="flex items-start gap-2.5 py-1.5">
<div
className="relative z-10 flex-shrink-0 w-5 h-5 mt-0.5 flex items-center justify-center rounded-full"
className="relative z-10 flex-shrink-0 w-[18px] h-[18px] flex items-center justify-center rounded-full"
style={{ background: "var(--color-bg)" }}
>
{status === "running" ? (
<span
className="w-4 h-4 border-[1.5px] rounded-full animate-spin"
style={{
borderColor: "var(--color-border-strong)",
borderTopColor: "var(--color-accent)",
}}
/>
) : status === "error" ? (
{status === "error" ? (
<ErrorCircleIcon />
) : (
<StepIcon kind={kind} />
<span className={status === "running" ? "animate-pulse" : ""}>
<StepIcon kind={kind} />
</span>
)}
</div>
<div className="flex-1 min-w-0">
<div
className="text-[13px] leading-snug flex items-start gap-2 flex-wrap"
className="text-[13px] leading-snug flex items-center gap-2 flex-wrap"
style={{
color:
status === "running"
@ -1287,7 +1259,7 @@ function ToolStep({
: "var(--color-text-secondary)",
}}
>
<span className="break-all">{label}</span>
<span className={`break-all${status === "running" ? " animate-pulse" : ""}`}>{label}</span>
{/* Exit code badge for exec tools */}
{kind === "exec" && status === "done" && output?.exitCode !== undefined && (
<span

View File

@ -155,6 +155,131 @@ function FileTypeIcon({ category }: { category: string }) {
}
}
function QueueItem({
msg,
idx,
onEdit,
onSendNow,
onRemove,
}: {
msg: QueuedMessage;
idx: number;
onEdit: (id: string, text: string) => void;
onSendNow: (id: string) => void;
onRemove: (id: string) => void;
}) {
const [editing, setEditing] = useState(false);
const [draft, setDraft] = useState(msg.text);
const inputRef = useRef<HTMLTextAreaElement>(null);
const autoResize = () => {
const el = inputRef.current;
if (!el) {return;}
el.style.height = "auto";
el.style.height = `${el.scrollHeight}px`;
};
useEffect(() => {
if (editing) {
inputRef.current?.focus();
const len = inputRef.current?.value.length ?? 0;
inputRef.current?.setSelectionRange(len, len);
autoResize();
}
}, [editing]);
const commitEdit = () => {
const trimmed = draft.trim();
if (trimmed && trimmed !== msg.text) {
onEdit(msg.id, trimmed);
} else {
setDraft(msg.text);
}
setEditing(false);
};
return (
<div
className={`flex items-start gap-2.5 group py-2 ${idx > 0 ? "border-t" : ""}`}
style={idx > 0 ? { borderColor: "var(--color-border)" } : undefined}
>
<span
className="shrink-0 mt-px text-[11px] font-medium tabular-nums w-4"
style={{ color: "var(--color-text-muted)" }}
>
{idx + 1}
</span>
{editing ? (
<textarea
ref={inputRef}
className="flex-1 text-[13px] leading-[1.45] min-w-0 resize-none rounded-md px-2 py-1 outline-none"
style={{
color: "var(--color-text-secondary)",
background: "var(--color-bg)",
border: "1px solid var(--color-border)",
}}
rows={1}
value={draft}
onChange={(e) => { setDraft(e.target.value); autoResize(); }}
onBlur={commitEdit}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); commitEdit(); }
if (e.key === "Escape") { setDraft(msg.text); setEditing(false); }
}}
/>
) : (
<p
className="flex-1 text-[13px] leading-[1.45] line-clamp-2 min-w-0"
style={{ color: "var(--color-text-secondary)", whiteSpace: "pre-wrap" }}
>
{msg.text || (msg.attachedFiles.length > 0 ? `${msg.attachedFiles.length} file(s)` : "")}
</p>
)}
{!editing && (
<div className="flex items-center gap-1 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
{/* Edit */}
<button
type="button"
className="rounded-md p-1 transition-colors hover:bg-stone-100 dark:hover:bg-stone-800"
title="Edit message"
onClick={() => { setDraft(msg.text); setEditing(true); }}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-stone-400">
<path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
<path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z" />
</svg>
</button>
{/* Send now */}
<button
type="button"
className="rounded-md p-1 transition-colors hover:bg-stone-100 dark:hover:bg-stone-800"
title="Send now"
onClick={() => onSendNow(msg.id)}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-stone-400">
<path d="M12 19V5" />
<path d="m5 12 7-7 7 7" />
</svg>
</button>
{/* Delete */}
<button
type="button"
className="rounded-md p-1 transition-colors hover:bg-stone-100 dark:hover:bg-stone-800"
title="Remove from queue"
onClick={() => onRemove(msg.id)}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-stone-400">
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
</button>
</div>
)}
</div>
);
}
function AttachmentStrip({
files,
compact,
@ -1272,6 +1397,10 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(
setQueuedMessages((prev) => prev.filter((m) => m.id !== id));
}, []);
const updateQueuedMessageText = useCallback((id: string, text: string) => {
setQueuedMessages((prev) => prev.map((m) => m.id === id ? { ...m, text } : m));
}, []);
/** Force-send: stop the agent, then immediately submit this queued message. */
const forceSendQueuedMessage = useCallback(
async (id: string) => {
@ -1361,13 +1490,12 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(
return (
<div
ref={scrollContainerRef}
className="h-full overflow-y-auto [scrollbar-gutter:stable]"
className="h-full flex flex-col"
style={{ background: "var(--color-main-bg)" }}
>
<div className="flex flex-col min-h-full">
{/* Header — sticky glass bar */}
<header
className={`${compact ? "px-3 py-2" : "px-3 py-2 md:px-6 md:py-3"} flex items-center justify-between sticky top-0 z-20 backdrop-blur-md`}
className={`${compact ? "px-3 py-2" : "px-3 py-2 md:px-6 md:py-3"} flex items-center justify-between z-20`}
style={{
background: "var(--color-bg-glass)",
}}
@ -1424,6 +1552,7 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(
variant="destructive"
onSelect={() => onDeleteSession(currentSessionId)}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18" /><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" /><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" /></svg>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
@ -1460,7 +1589,7 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(
{/* File-scoped session tabs (compact mode) */}
{compact && fileContext && fileSessions.length > 0 && (
<div
className="px-2 py-1.5 border-b flex gap-1 overflow-x-auto sticky top-[41px] z-20 backdrop-blur-md"
className="px-2 py-1.5 border-b flex gap-1 overflow-x-auto z-20"
style={{
borderColor: "var(--color-border)",
background: "var(--color-bg-glass)",
@ -1497,9 +1626,13 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(
</div>
)}
<div
ref={scrollContainerRef}
className="flex-1 overflow-y-auto min-h-0"
>
{/* Messages */}
<div
className={`flex-1 ${compact ? "px-3" : "px-6"}`}
className={`${compact ? "px-3" : "px-6"}`}
>
{loadingSession ? (
<div className="flex items-center justify-center h-full min-h-[60vh]">
@ -1620,10 +1753,11 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(
<p className="text-xs">{error.message}</p>
</div>
)}
</div>
{/* Input — sticky glass bar at bottom */}
{/* Input bar at bottom */}
<div
className={`${compact ? "px-3 py-2" : "px-3 pb-3 pt-0 md:px-6 md:pb-5"} sticky bottom-0 z-20 backdrop-blur-md`}
className={`${compact ? "px-3 py-2" : "px-3 pb-3 pt-0 md:px-6 md:pb-5"} z-20`}
style={{ background: "var(--color-bg-glass)" }}
>
<div
@ -1700,47 +1834,14 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(
</div>
<div className="flex flex-col p-2">
{queuedMessages.map((msg, idx) => (
<div
<QueueItem
key={msg.id}
className={`flex items-start gap-2.5 group py-2 ${idx > 0 ? "border-t" : ""}`}
style={idx > 0 ? { borderColor: "var(--color-border)" } : undefined}
>
<span
className="shrink-0 mt-px text-[11px] font-medium tabular-nums w-4"
style={{ color: "var(--color-text-muted)" }}
>
{idx + 1}
</span>
<p
className="flex-1 text-[13px] leading-[1.45] line-clamp-2 min-w-0"
style={{ color: "var(--color-text-secondary)", whiteSpace: "pre-wrap" }}
>
{msg.text || (msg.attachedFiles.length > 0 ? `${msg.attachedFiles.length} file(s)` : "")}
</p>
<div className="flex items-center gap-0.5 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
<button
type="button"
className="rounded-md px-1.5 py-0.5 text-[11px] font-medium transition-colors"
style={{ color: "var(--color-accent)", background: "var(--color-accent-light)" }}
title="Stop agent and send this message now"
onClick={() => forceSendQueuedMessage(msg.id)}
>
Send now
</button>
<button
type="button"
className="rounded-md p-0.5 transition-colors hover:opacity-80"
style={{ color: "var(--color-text-muted)" }}
title="Remove from queue"
onClick={() => removeQueuedMessage(msg.id)}
>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
</div>
</div>
msg={msg}
idx={idx}
onEdit={updateQueuedMessageText}
onSendNow={forceSendQueuedMessage}
onRemove={removeQueuedMessage}
/>
))}
</div>
</div>
@ -1814,9 +1915,9 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(
</svg>
</button>
</div>
{/* Send / Stop button (single button, toggles role) */}
{/* Send / Stop / Queue buttons */}
<div className="flex items-center gap-1.5">
{isStreaming ? (
{isStreaming && (
<button
type="button"
onClick={() => handleStop()}
@ -1836,6 +1937,37 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(
<rect width="10" height="10" rx="1.5" />
</svg>
</button>
)}
{isStreaming ? (
<button
type="button"
onClick={() => {
editorRef.current?.submit();
}}
disabled={
(editorEmpty &&
attachedFiles.length === 0) ||
loadingSession
}
className="h-7 px-3 rounded-full flex items-center gap-1.5 text-[12px] font-medium disabled:opacity-40 disabled:cursor-not-allowed"
style={{
background:
!editorEmpty || attachedFiles.length > 0
? "var(--color-accent)"
: "var(--color-surface-hover)",
color:
!editorEmpty || attachedFiles.length > 0
? "white"
: "var(--color-text-muted)",
}}
title="Add to queue"
>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="9 10 4 15 9 20" />
<path d="M20 4v7a4 4 0 0 1-4 4H4" />
</svg>
Queue
</button>
) : (
<button
type="button"
@ -1881,7 +2013,6 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(
</div>
</div>
</div>
</div>
{/* File picker modal */}
<FilePickerModal

View File

@ -22,10 +22,15 @@ function DropdownMenuPortal({
}
function DropdownMenuTrigger({
className,
...props
}: React.ComponentProps<typeof MenuPrimitive.Trigger>) {
return (
<MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
<MenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
className={cn("cursor-pointer outline-none ring-0 border-none", className)}
{...props}
/>
);
}
@ -53,9 +58,8 @@ function DropdownMenuContent({
<MenuPrimitive.Popup
data-slot="dropdown-menu-content"
className={cn(
"min-w-32 rounded-lg p-1 shadow-md duration-100 overflow-x-hidden overflow-y-auto outline-none",
"bg-[var(--color-surface)] text-[var(--color-text)] border border-[var(--color-border)]",
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
"bg-neutral-100/[0.67] border border-white backdrop-blur-md text-[var(--color-text)] z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-3xl p-1 shadow-[0_0_25px_0_rgba(0,0,0,0.16)] outline-none",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
@ -85,8 +89,8 @@ function DropdownMenuLabel({
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
"px-1.5 py-1 text-xs font-medium text-[var(--color-text-muted)]",
inset && "pl-7",
"px-2 py-1.5 text-sm font-medium",
inset && "pl-8",
className,
)}
{...props}
@ -116,11 +120,10 @@ function DropdownMenuItem({
data-inset={inset}
data-variant={variant}
className={cn(
"gap-1.5 rounded-md px-1.5 py-1 text-sm relative flex cursor-default items-center outline-none select-none",
"focus:bg-[var(--color-surface-hover)] focus:text-[var(--color-text)]",
"data-[variant=destructive]:text-[var(--color-error)] data-[variant=destructive]:focus:bg-[var(--color-error)]/10 data-[variant=destructive]:focus:text-[var(--color-error)]",
inset && "pl-7",
"[&_svg]:pointer-events-none [&_svg]:shrink-0",
"bg-transparent hover:bg-neutral-400/15 text-sm transition-all relative flex cursor-pointer items-center gap-2 rounded-full px-2 py-1.5 outline-none ring-0 border-none select-none",
"data-[variant=destructive]:text-[var(--color-error)] data-[variant=destructive]:hover:bg-[var(--color-error)]/10 data-[variant=destructive]:hover:text-[var(--color-error)]",
inset && "pl-8",
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"data-disabled:pointer-events-none data-disabled:opacity-50",
className,
)}
@ -151,10 +154,9 @@ function DropdownMenuSubTrigger({
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-[var(--color-surface-hover)] focus:text-[var(--color-text)] data-open:bg-[var(--color-surface-hover)] data-open:text-[var(--color-text)]",
"gap-1.5 rounded-md px-1.5 py-1 text-sm flex cursor-default items-center outline-none select-none",
inset && "pl-7",
"[&_svg]:pointer-events-none [&_svg]:shrink-0",
"bg-transparent hover:bg-neutral-400/15 focus:bg-neutral-400/15 data-open:bg-neutral-400/15 flex cursor-pointer items-center gap-2 rounded-full px-2 py-1.5 text-sm outline-none select-none transition-all focus:ring-0 focus:outline-none focus-visible:ring-0 focus-visible:outline-none",
inset && "pl-8",
"[&_svg:not([class*='text-'])]:text-[var(--color-text-muted)] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
@ -200,20 +202,16 @@ function DropdownMenuCheckboxItem({
data-slot="dropdown-menu-checkbox-item"
data-inset={inset}
className={cn(
"focus:bg-[var(--color-surface-hover)] focus:text-[var(--color-text)]",
"gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm relative flex cursor-default items-center outline-none select-none",
inset && "pl-7",
"[&_svg]:pointer-events-none [&_svg]:shrink-0",
"hover:bg-neutral-400/15 focus:bg-neutral-400/15 relative flex cursor-pointer items-center gap-2 rounded-full py-1.5 pr-2 pl-8 text-sm outline-none select-none transition-colors",
inset && "pl-8",
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"data-disabled:pointer-events-none data-disabled:opacity-50",
className,
)}
checked={checked}
{...props}
>
<span
className="absolute right-2 flex items-center justify-center pointer-events-none"
data-slot="dropdown-menu-checkbox-item-indicator"
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<MenuPrimitive.CheckboxItemIndicator>
<CheckIcon className="size-4" />
</MenuPrimitive.CheckboxItemIndicator>
@ -247,19 +245,15 @@ function DropdownMenuRadioItem({
data-slot="dropdown-menu-radio-item"
data-inset={inset}
className={cn(
"focus:bg-[var(--color-surface-hover)] focus:text-[var(--color-text)]",
"gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm relative flex cursor-default items-center outline-none select-none",
inset && "pl-7",
"[&_svg]:pointer-events-none [&_svg]:shrink-0",
"hover:bg-neutral-400/15 focus:bg-neutral-400/15 relative flex cursor-pointer items-center gap-2 rounded-full py-1.5 pr-2 pl-8 text-sm outline-none select-none transition-colors",
inset && "pl-8",
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"data-disabled:pointer-events-none data-disabled:opacity-50",
className,
)}
{...props}
>
<span
className="absolute right-2 flex items-center justify-center pointer-events-none"
data-slot="dropdown-menu-radio-item-indicator"
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<MenuPrimitive.RadioItemIndicator>
<CheckIcon className="size-4" />
</MenuPrimitive.RadioItemIndicator>
@ -277,7 +271,7 @@ function DropdownMenuSeparator({
<MenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn(
"bg-[var(--color-border)] -mx-1 my-1 h-px",
"bg-neutral-400/15 -mx-1 my-1 h-px",
className,
)}
{...props}

View File

@ -347,6 +347,7 @@ export function ChatSessionsSidebar({
variant="destructive"
onSelect={() => handleDeleteSession(session.id)}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18" /><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" /><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" /></svg>
Delete
</DropdownMenuItem>
</DropdownMenuContent>

View File

@ -8,6 +8,8 @@
/* Background / Surface */
--color-bg: #f5f5f4;
--color-surface: #ffffff;
--color-sidebar-bg: #fafaf9;
--color-main-bg: rgba(250, 250, 249, 0.5);
--color-surface-hover: #f5f4f1;
--color-surface-raised: #ffffff;
@ -43,7 +45,7 @@
/* Glassmorphism */
--color-glass: rgba(255, 255, 255, 0.72);
--color-glass-border: rgba(255, 255, 255, 0.85);
--color-bg-glass: rgba(245, 245, 244, 0.8);
--color-bg-glass: rgba(250, 250, 249, 0.8);
/* Object type chips */
--color-chip-object: rgba(37, 99, 235, 0.08);
@ -74,6 +76,8 @@
/* Background / Surface */
--color-bg: #0c0c0b;
--color-surface: #161615;
--color-sidebar-bg: #141413;
--color-main-bg: #161615;
--color-surface-hover: #1e1e1c;
--color-surface-raised: #1a1a18;
@ -240,6 +244,21 @@ a,
Scrollbar
============================================================ */
/* Base UI menu — remove default focus outlines */
[data-slot="dropdown-menu-content"],
[data-slot="dropdown-menu-item"],
[data-slot="dropdown-menu-sub-content"],
[data-slot="dropdown-menu-checkbox-item"],
[data-slot="dropdown-menu-radio-item"],
[data-slot="dropdown-menu-sub-trigger"] {
outline: none !important;
border: none !important;
}
[data-slot="dropdown-menu-content"] {
border: 1px solid white !important;
}
::-webkit-scrollbar {
width: 6px;
height: 6px;

View File

@ -223,8 +223,8 @@ function ResizeHandle({
role="separator"
aria-orientation="vertical"
onMouseDown={onMouseDown}
className={`shrink-0 w-1 cursor-col-resize flex justify-center transition-colors ${showHover ? "bg-blue-600/30" : "hover:bg-blue-600/30"}`}
style={{ minWidth: 4 }}
className={`cursor-col-resize flex justify-center transition-colors ${showHover ? "bg-blue-600/30" : "hover:bg-blue-600/30"}`}
style={{ position: "absolute", [mode === "left" ? "right" : "left"]: -2, top: 0, bottom: 0, width: 4, zIndex: 20 }}
/>
);
}
@ -1253,7 +1253,7 @@ function WorkspacePageInner() {
<div
ref={layoutRef}
className="flex h-screen"
style={{ background: "var(--color-bg)" }}
style={{ background: "var(--color-main-bg)" }}
onClick={handleContainerClick}
>
{/* Left sidebar — static on desktop (resizable), drawer overlay on mobile */}
@ -1283,9 +1283,16 @@ function WorkspacePageInner() {
) : (
<>
<div
className="flex shrink-0 flex-col"
className="flex shrink-0 flex-col relative"
style={{ width: leftSidebarWidth, minWidth: leftSidebarWidth }}
>
<ResizeHandle
mode="left"
containerRef={layoutRef}
min={LEFT_SIDEBAR_MIN}
max={LEFT_SIDEBAR_MAX}
onResize={setLeftSidebarWidth}
/>
<WorkspaceSidebar
tree={enhancedTree}
activePath={activePath}
@ -1306,18 +1313,11 @@ function WorkspacePageInner() {
width={leftSidebarWidth}
/>
</div>
<ResizeHandle
mode="left"
containerRef={layoutRef}
min={LEFT_SIDEBAR_MIN}
max={LEFT_SIDEBAR_MAX}
onResize={setLeftSidebarWidth}
/>
</>
)}
{/* Main content */}
<main className="flex-1 flex flex-col min-w-0 overflow-hidden">
<main className="flex-1 flex flex-col min-w-0 overflow-hidden" style={{ background: "var(--color-main-bg)" }}>
{/* Mobile top bar — always visible on mobile */}
{isMobile && (
<div
@ -1426,7 +1426,7 @@ function WorkspacePageInner() {
{showMainChat ? (
/* Main chat view (default when no file is selected) */
<>
<div className="flex-1 flex flex-col min-w-0">
<div className="flex-1 flex flex-col min-w-0" style={{ background: "var(--color-main-bg)" }}>
{activeSubagent ? (
<SubagentPanel
sessionKey={activeSubagent.childSessionKey}
@ -1483,17 +1483,17 @@ function WorkspacePageInner() {
)
) : (
<>
<ResizeHandle
mode="right"
containerRef={layoutRef}
min={RIGHT_SIDEBAR_MIN}
max={RIGHT_SIDEBAR_MAX}
onResize={setRightSidebarWidth}
/>
<div
className="flex shrink-0 flex-col"
style={{ width: rightSidebarWidth, minWidth: rightSidebarWidth }}
className="flex shrink-0 flex-col relative"
style={{ width: rightSidebarWidth, minWidth: rightSidebarWidth, background: "var(--color-sidebar-bg)" }}
>
<ResizeHandle
mode="right"
containerRef={layoutRef}
min={RIGHT_SIDEBAR_MIN}
max={RIGHT_SIDEBAR_MAX}
onResize={setRightSidebarWidth}
/>
{chatSidebarPreview ? (
<ChatSidebarPreview
preview={chatSidebarPreview}
@ -1556,21 +1556,21 @@ function WorkspacePageInner() {
{/* Chat sidebar (file/folder-scoped) — hidden for reserved paths, hidden on mobile */}
{!isMobile && fileContext && showChatSidebar && (
<>
<ResizeHandle
mode="right"
containerRef={layoutRef}
min={RIGHT_SIDEBAR_MIN}
max={RIGHT_SIDEBAR_MAX}
onResize={setRightSidebarWidth}
/>
<aside
className="flex-shrink-0 border-l flex flex-col"
className="flex-shrink-0 border-l flex flex-col relative"
style={{
width: rightSidebarWidth,
borderColor: "var(--color-border)",
background: "var(--color-bg)",
}}
>
<ResizeHandle
mode="right"
containerRef={layoutRef}
min={RIGHT_SIDEBAR_MIN}
max={RIGHT_SIDEBAR_MAX}
onResize={setRightSidebarWidth}
/>
<ChatPanel
ref={compactChatRef}
compact

File diff suppressed because one or more lines are too long