batch4.1-5.1: dedup audit, archive 400 fix, sidebar Delete, landing-page enrichment, auto-name tool-call fix
- Fastify global empty-JSON-body parser fixes archive/unarchive/stop 400s - Removed redundant local sessionEvents.emit at all 5+2 sites with server-side WS publishers; added dedupe guards in useSidebar/Workspace/Project handlers - Sidebar session right-click adds Delete (destructive) with confirm Dialog - Session.tsx navigates away on session_deleted/session_archived for the active session - SessionLandingPage chat rows show message_count, effective_context_tokens, last_message_preview via LATERAL joins on GET /api/sessions/:id/chats - Workspace.tsx pane drag-to-reorder using native HTML5 events (no new deps) - CompactCard: Copy toast, Send-to-chat with target chat name, empty-state in share popover, Re-run button - auto_name.ts: filter count gate and assistant-fetch by content <> '' so tool-call assistant rows don't trip the once-and-only-once guard - Adds CLAUDE.md and apps/web/src/lib/format.ts Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import { MessageSquare, Send, ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import type { Chat } from '@/api/types';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { formatTokens } from '@/lib/format';
|
||||
|
||||
interface Props {
|
||||
sessionId: string;
|
||||
@@ -27,6 +28,51 @@ function relTime(iso: string): string {
|
||||
return `${day}d ago`;
|
||||
}
|
||||
|
||||
function ChatRow({
|
||||
chat,
|
||||
onClick,
|
||||
dimmed,
|
||||
trailing,
|
||||
}: {
|
||||
chat: Chat;
|
||||
onClick: () => void;
|
||||
dimmed?: boolean;
|
||||
trailing?: string;
|
||||
}) {
|
||||
const meta: string[] = [relTime(chat.updated_at)];
|
||||
if (chat.message_count !== undefined && chat.message_count > 0) {
|
||||
meta.push(`${chat.message_count} msg`);
|
||||
}
|
||||
const tokens = formatTokens(chat.effective_context_tokens);
|
||||
if (tokens) meta.push(tokens);
|
||||
const preview = chat.last_message_preview;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="w-full flex flex-col gap-0.5 px-3 py-2 hover:bg-muted/50 text-left"
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<MessageSquare className={`size-3.5 shrink-0 ${dimmed ? 'opacity-40' : 'opacity-70'}`} />
|
||||
<span className={`truncate text-sm flex-1 ${dimmed ? 'text-muted-foreground' : ''}`}>
|
||||
{chat.name ?? 'New chat'}
|
||||
</span>
|
||||
{trailing && (
|
||||
<span className="text-xs text-muted-foreground shrink-0">{trailing}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-5 text-xs text-muted-foreground tabular-nums">
|
||||
{meta.join(' · ')}
|
||||
</div>
|
||||
{preview && (
|
||||
<div className="ml-5 text-xs italic text-muted-foreground truncate">
|
||||
{preview}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function SessionLandingPage({
|
||||
chats,
|
||||
onOpenChat,
|
||||
@@ -50,6 +96,10 @@ export function SessionLandingPage({
|
||||
setComposerValue('');
|
||||
}
|
||||
|
||||
// TODO: Landing page chat counts are a snapshot at mount. New messages in
|
||||
// visible chats won't update the per-row stats until next mount/navigation.
|
||||
// Wiring WS reactivity through here is deferred (rare use case: user is in
|
||||
// a pane when messages stream, not on the landing page).
|
||||
return (
|
||||
<div className="flex flex-col h-full min-h-0">
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-6">
|
||||
@@ -60,19 +110,7 @@ export function SessionLandingPage({
|
||||
<ul className="divide-y rounded-md border">
|
||||
{openChats.map((chat) => (
|
||||
<li key={chat.id}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onOpenChat(chat.id)}
|
||||
className="w-full flex items-center gap-2 px-3 py-2 hover:bg-muted/50 text-left"
|
||||
>
|
||||
<MessageSquare className="size-3.5 opacity-70 shrink-0" />
|
||||
<span className="truncate text-sm flex-1">
|
||||
{chat.name ?? 'New chat'}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground shrink-0 tabular-nums">
|
||||
{relTime(chat.updated_at)}
|
||||
</span>
|
||||
</button>
|
||||
<ChatRow chat={chat} onClick={() => onOpenChat(chat.id)} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -94,19 +132,12 @@ export function SessionLandingPage({
|
||||
<ul className="divide-y rounded-md border">
|
||||
{closedChats.map((chat) => (
|
||||
<li key={chat.id}>
|
||||
<button
|
||||
type="button"
|
||||
<ChatRow
|
||||
chat={chat}
|
||||
onClick={() => void onReopenChat(chat.id)}
|
||||
className="w-full flex items-center gap-2 px-3 py-2 hover:bg-muted/50 text-left"
|
||||
>
|
||||
<MessageSquare className="size-3.5 opacity-40 shrink-0" />
|
||||
<span className="truncate text-sm flex-1 text-muted-foreground">
|
||||
{chat.name ?? 'New chat'}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground shrink-0">
|
||||
Reopen
|
||||
</span>
|
||||
</button>
|
||||
dimmed
|
||||
trailing="Reopen"
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user