feat(web): workspace components — ComparePane, Memory page, McpDialog, error boundaries, message-parts

- Add ComparePane.tsx: side-by-side AI response comparison
- Add Memory.tsx: memory management page with CRUD UI
- Add McpPermissionDialog.tsx: MCP tool permission approval dialog
- Add McpResponseDisplay.tsx: MCP response visualization
- Add MessageBoundary.tsx + MessageListErrorBoundary.tsx: error resilience
- Add EmptyState.tsx: contextual empty state component
- Add KeyboardShortcutsDialog.tsx: keyboard shortcut reference
- Add message-parts/: ActionRow, CompactCard, MistakeRecoverySentinel, ReasoningBlock, SendToTerminalMenu, StatsLine, SummaryCard
- Add useDraftPersistence.ts: draft message persistence hook
- Add useTerminals.ts: terminal session management hook
- Add keyboard-shortcuts.ts + tool-utils.ts: shared utilities
- Extend components: ChatInput, MessageBubble, MessageList, Workspace, panes
- Extend hooks: useTerminalSocket, useSessionStream test suite
- Update pages: Home, Project — workspace layout and session flow
This commit is contained in:
2026-06-08 03:49:22 +00:00
parent 51733c1338
commit 50de80ee75
51 changed files with 3352 additions and 96 deletions

View File

@@ -13,7 +13,7 @@ import { useViewport } from '@/hooks/useViewport';
import { formatModelLabel } from '@/lib/model-label';
interface Props {
value: string;
value: string | null;
onChange: (model: string) => void | Promise<void>;
}
@@ -27,7 +27,7 @@ function ModelList({
}: {
models: ModelInfo[] | null;
error: string | null;
value: string;
value: string | null;
onPick: (id: string) => void;
}) {
if (error) {
@@ -82,8 +82,8 @@ export function ModelPicker({ value, onChange }: Props) {
<button
type="button"
onClick={() => setOpen(true)}
aria-label={`Model: ${value}`}
title={value}
aria-label={`Model: ${value ?? 'default'}`}
title={value ?? undefined}
className="inline-flex items-center justify-center min-h-[36px] min-w-[44px] rounded text-muted-foreground hover:text-foreground"
>
<Cpu className="size-4" />
@@ -104,7 +104,7 @@ export function ModelPicker({ value, onChange }: Props) {
type="button"
className="text-xs font-mono text-muted-foreground hover:text-foreground flex items-center gap-1 px-1.5 py-0.5 rounded hover:bg-muted/60"
>
{formatModelLabel(value)}
{value ? formatModelLabel(value) : 'Model'}
<ChevronDown className="size-3 opacity-70" />
</button>
</DropdownMenuTrigger>