tab-close + chat archive/delete + landing-card buttons + 1000px content cap

Feature 1 — Tab close menu (pure local pane state, no API):
- ChatTabBar context menu: Rename / sep / Close / Close others / Close to right / Close all
- Workspace bulk-tab primitives: closeOtherTabs, closeTabsToRight, closeAllTabs (manipulate panes[].chatIds, no fetch)
- Drop in-bar Delete; landing card's name-typed Delete is the canonical destructive path

Feature 2 — Chat archive + delete:
- chats.status vocabulary aligned with projects ('open' | 'archived'); DROP old inline CHECK, UPDATE 'closed' → 'archived', ADD new named chats_status_chk
- POST /api/chats/:id/archive (204) + POST /api/chats/:id/unarchive (200) + GET /api/sessions/:id/chats?status=archived; DELETE publishes chat_deleted; PATCH simplified to name-only
- 3 new WS frames: chat_archived, chat_unarchived, chat_deleted (renamed from chat_closed)
- Same dedup discipline: server-only publish, no local sessionEvents.emit in client
- SessionLandingPage: right-click ContextMenu (Open / Rename / Archive / sep / Delete-destructive), inline rename, archive confirm dialog, delete dialog with name-typed Input gated until typed text === chat.name, Archived chats collapsible section with Restore
- Card-level Archive + Delete icon buttons reusing the same dialog state setters; stopPropagation on both so card click still opens the chat; archived cards keep only Restore

UX — chat content width cap:
- ChatPane content (MessageList, queue chips, stop button, ChatInput) wrapped in inner max-w-[1000px] mx-auto w-full so messages center; outer border-t / scroll containers stay full-width so pane chrome and backgrounds remain edge-to-edge
- No new deps, no media queries (narrow viewports collapse to width naturally)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 03:21:26 +00:00
parent 48a972e139
commit e09c67d65c
13 changed files with 475 additions and 139 deletions

View File

@@ -77,8 +77,19 @@ export interface ChatUpdatedEvent {
updated_at: string;
}
export interface ChatClosedEvent {
type: 'chat_closed';
export interface ChatArchivedEvent {
type: 'chat_archived';
chat_id: string;
session_id: string;
}
export interface ChatUnarchivedEvent {
type: 'chat_unarchived';
chat: Chat;
}
export interface ChatDeletedEvent {
type: 'chat_deleted';
chat_id: string;
session_id: string;
}
@@ -112,7 +123,9 @@ export type SessionEvent =
| SessionArchivedEvent
| ChatCreatedEvent
| ChatUpdatedEvent
| ChatClosedEvent
| ChatArchivedEvent
| ChatUnarchivedEvent
| ChatDeletedEvent
| ProjectArchivedEvent
| ProjectUnarchivedEvent
| ProjectUpdatedEvent;

View File

@@ -165,7 +165,9 @@ function applyEvent(prev: SidebarResponse, event: import('./sessionEvents').Sess
}
case 'chat_created':
case 'chat_updated':
case 'chat_closed':
case 'chat_archived':
case 'chat_unarchived':
case 'chat_deleted':
return prev;
case 'project_archived': {
const next = prev.projects.filter((p) => p.id !== event.project_id);