feat: massive chat reliability + UX pass (web/desktop/mobile/vscode) (#593)
## Added Features - Add VS Code save-as-image flow for assistant messages via webview bridge + native save dialog. - Add hourly desktop update checks after startup. - Add new tool output display mode: `Changes` (auto-expand edit/write/patch only; keep activity expanded; mode guidance text). - Add GitHub PR attachment flow in chat input with PR picker + attached PR chip/details. - Add mobile overlay presentation for GitHub Issue and PR pickers (shared with desktop picker content). ## Fixes - Save-gate project icon updates until explicit Save; allow icon removal with same save-gated behavior. - Restore clickable chat action buttons in sticky header mode (desktop + Firefox hit-target issue). - Clamp sticky user messages to bounded chat height and allow internal scrolling. - Prevent drawer context crash during iPad/tablet orientation switching. - Improve text-selection action menu placement on narrow screens. - Move assistant message time into clock tooltip; keep duration display clean. - Hide `Link GitHub Issue` row in VS Code chat input area (GitHub flow is not yet ready there). - Remove laggy close animation in text-selection popover; keep open motion/positioning behavior. - Fetch branches when picker opens and cache empty; show loading state instead of false “No branches found”. - Fix share-image export metadata rendering (theme background resolution, timestamp rendering, footer alignment). - Scope MCP services status/toggles to active directory to avoid cross-project leakage. - Improve long user-message clamp behavior (40% cap variant, hidden scrollbar, scroll shadows, expansion detection). - Fix desktop `Check for Updates` menu handler; prevent duplicate checks; show clear success/error toasts. - Stabilize long user-message scrolling behavior (follow-up hardening). - Avoid premature web update failure on slower servers. - Restore user message image previews + fullscreen gallery navigation payload. - Repair desktop chat drag-and-drop image attachments when native drop coords are missing. - Move GitHub issue linking entry into Add attachment menu. - Align header context usage percentage visuals with context panel. - Align `@` file search with active project in all runtimes. - Route `@` file discovery through OpenCode SDK `find.files`; remove legacy `/api/fs/search` reliance. - Make chat `@` mention behavior consistent with files-style behavior. - Keep status-row todos in stable order after status changes; add compact status icons; replace noisy priority labels. ## Refactors / UX Consistency - Simplify chat attachment model and remove project file picker path. - Keep composer focused on `@` mention file flow. - Use direct `Attach files` action in VS Code instead of attachment dropdown path. - Unify issue/PR picker behavior between desktop and mobile overlays.
This commit is contained in:
committed by
GitHub
parent
ca18b8be0f
commit
79143bff4c
@@ -1,7 +1,15 @@
|
||||
import React from "react";
|
||||
import { RiArrowUpSLine, RiArrowDownSLine, RiCloseCircleLine } from "@remixicon/react";
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiArrowUpDoubleLine,
|
||||
RiArrowUpSLine,
|
||||
RiCheckboxCircleLine,
|
||||
RiCloseCircleLine,
|
||||
RiRecordCircleLine,
|
||||
RiTimeLine,
|
||||
} from "@remixicon/react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTodoStore, type TodoItem, type TodoStatus } from "@/stores/useTodoStore";
|
||||
import { useTodoStore, type TodoItem, type TodoPriority, type TodoStatus } from "@/stores/useTodoStore";
|
||||
import { useSessionStore } from "@/stores/useSessionStore";
|
||||
import { useUIStore } from "@/stores/useUIStore";
|
||||
import { WorkingPlaceholder } from "./message/parts/WorkingPlaceholder";
|
||||
@@ -22,6 +30,18 @@ const statusConfig: Record<TodoStatus, { textClassName: string }> = {
|
||||
},
|
||||
};
|
||||
|
||||
const priorityClassName: Record<TodoPriority, string> = {
|
||||
high: "text-[var(--status-warning)]",
|
||||
medium: "text-muted-foreground",
|
||||
low: "text-muted-foreground/70",
|
||||
};
|
||||
|
||||
const priorityIcon: Record<TodoPriority, React.ReactNode> = {
|
||||
high: <RiArrowUpDoubleLine className="h-3.5 w-3.5" aria-hidden="true" />,
|
||||
medium: <RiArrowUpSLine className="h-3.5 w-3.5" aria-hidden="true" />,
|
||||
low: <RiArrowDownSLine className="h-3.5 w-3.5" aria-hidden="true" />,
|
||||
};
|
||||
|
||||
interface TodoItemRowProps {
|
||||
todo: TodoItem;
|
||||
}
|
||||
@@ -29,8 +49,18 @@ interface TodoItemRowProps {
|
||||
const TodoItemRow: React.FC<TodoItemRowProps> = ({ todo }) => {
|
||||
const config = statusConfig[todo.status] || statusConfig.pending;
|
||||
|
||||
const statusIcon =
|
||||
todo.status === "in_progress" ? (
|
||||
<RiRecordCircleLine className="h-3.5 w-3.5 text-[var(--status-info)]" aria-hidden="true" />
|
||||
) : todo.status === "completed" ? (
|
||||
<RiCheckboxCircleLine className="h-3.5 w-3.5 text-[var(--status-success)]" aria-hidden="true" />
|
||||
) : (
|
||||
<RiTimeLine className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex items-start min-w-0 py-0.5">
|
||||
<div className="flex items-start min-w-0 py-0.5 gap-2">
|
||||
<span className="mt-0.5 flex-shrink-0">{statusIcon}</span>
|
||||
<span
|
||||
className={cn(
|
||||
"flex-1 typography-ui-label",
|
||||
@@ -39,6 +69,15 @@ const TodoItemRow: React.FC<TodoItemRowProps> = ({ todo }) => {
|
||||
>
|
||||
{todo.content}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"typography-meta flex-shrink-0",
|
||||
priorityClassName[todo.priority] ?? priorityClassName.medium
|
||||
)}
|
||||
title={`${todo.priority} priority`}
|
||||
>
|
||||
{priorityIcon[todo.priority] ?? priorityIcon.medium}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -89,18 +128,10 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
}
|
||||
}, [currentSessionId, loadTodos]);
|
||||
|
||||
// Filter out cancelled todos for display, sort by status priority
|
||||
// Filter out cancelled todos for display and keep original order.
|
||||
// This prevents items from jumping around when status changes.
|
||||
const visibleTodos = React.useMemo(() => {
|
||||
const statusOrder: Record<TodoStatus, number> = {
|
||||
in_progress: 0,
|
||||
pending: 1,
|
||||
completed: 2,
|
||||
cancelled: 3,
|
||||
};
|
||||
|
||||
return [...todos]
|
||||
.filter((todo) => todo.status !== "cancelled")
|
||||
.sort((a, b) => statusOrder[a.status] - statusOrder[b.status]);
|
||||
return todos.filter((todo) => todo.status !== "cancelled");
|
||||
}, [todos]);
|
||||
|
||||
// Find the current active todo (first in_progress, or first pending)
|
||||
@@ -119,6 +150,12 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
return { completed, total };
|
||||
}, [todos]);
|
||||
|
||||
const statusSummary = React.useMemo(() => {
|
||||
const active = visibleTodos.filter((t) => t.status === "in_progress").length;
|
||||
const left = visibleTodos.filter((t) => t.status === "in_progress" || t.status === "pending").length;
|
||||
return { active, left };
|
||||
}, [visibleTodos]);
|
||||
|
||||
const hasActiveTodos = visibleTodos.some((t) => t.status === "in_progress" || t.status === "pending");
|
||||
// Original logic from ChatInput
|
||||
const shouldRenderPlaceholder = !showAbortStatus && (wasAborted || !abortActive);
|
||||
@@ -178,7 +215,7 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
<span className="typography-ui-label">Tasks</span>
|
||||
)}
|
||||
<span className="typography-meta">
|
||||
{progress.completed}/{progress.total}
|
||||
{statusSummary.active} active · {statusSummary.left} left
|
||||
</span>
|
||||
{isExpanded ? (
|
||||
<RiArrowUpSLine className="h-3.5 w-3.5" />
|
||||
|
||||
Reference in New Issue
Block a user