Files
boocode/apps/web/src/hooks/sessionEvents.ts
indifferentketchup 59fe6f0522 v1.4-fork-header: fork from message + delete message + header polish + housekeeping
- Fork: POST /api/chats/:id/fork creates a new chat in the same session,
  copies messages up to target (status=complete) with row-offset
  clock_timestamp() for stable ordering. Client emits open_chat_in_active_pane
  event; Workspace opens it in the active pane. No maybeAutoNameChat on forks.
- Delete: DELETE /api/chats/:id/messages/:message_id with 409 if the chat is
  currently streaming. Cascading-forward delete (created_at >= target).
  MessageBubble Trash button + confirm Dialog.
- Header: Projects -> Project -> Session breadcrumb, model badge pill,
  inline session rename, active file path via new useActivePane() hook.
  Server now publishes session_renamed on PATCH /api/sessions/:id;
  client-side dup emit removed from Session.tsx.
- Housekeeping: NOW() -> clock_timestamp() in schema.sql defaults, dead
  PaneTab.tsx and panes/PaneShell.tsx removed, session_panes backfill
  INSERT removed (CREATE TABLE retained), Tailnet trust comment near
  app.listen().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 04:12:01 +00:00

159 lines
3.2 KiB
TypeScript

// Tiny in-app event bus for session metadata changes that need to propagate
// across hooks (e.g. AI rename arriving via WS in the session view needs to
// also refresh the sidebar's session list).
import type { Chat, Project, Session } from '@/api/types';
import type { Attachment } from '@/lib/attachments';
export interface SessionRenamedEvent {
type: 'session_renamed';
session_id: string;
name: string;
}
export interface ProjectCreatedEvent {
type: 'project_created';
project: Project;
}
export interface ProjectDeletedEvent {
type: 'project_deleted';
project_id: string;
}
export interface SessionCreatedEvent {
type: 'session_created';
session: Session;
project_id: string;
}
export interface SessionDeletedEvent {
type: 'session_deleted';
session_id: string;
project_id: string;
}
export interface SessionUpdatedEvent {
type: 'session_updated';
session_id: string;
project_id: string;
name: string;
updated_at: string;
}
export interface SessionLoadedEvent {
type: 'session_loaded';
session_id: string;
project_id: string;
}
export interface OpenFileInBrowserEvent {
type: 'open_file_in_browser';
path: string; // project-relative
}
export interface AttachChatFileEvent {
type: 'attach_chat_file';
attachment: Omit<Attachment, 'id'>;
}
export interface OpenChatInActivePaneEvent {
type: 'open_chat_in_active_pane';
chat_id: string;
}
export interface SessionArchivedEvent {
type: 'session_archived';
session_id: string;
project_id: string;
}
export interface ChatCreatedEvent {
type: 'chat_created';
chat: Chat;
session_id: string;
}
export interface ChatUpdatedEvent {
type: 'chat_updated';
chat_id: string;
session_id: string;
name: string | null;
updated_at: string;
}
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;
}
export interface ProjectArchivedEvent {
type: 'project_archived';
project_id: string;
}
export interface ProjectUnarchivedEvent {
type: 'project_unarchived';
project: Project;
}
export interface ProjectUpdatedEvent {
type: 'project_updated';
project_id: string;
name: string;
}
export type SessionEvent =
| SessionRenamedEvent
| ProjectCreatedEvent
| ProjectDeletedEvent
| SessionCreatedEvent
| SessionDeletedEvent
| SessionUpdatedEvent
| SessionLoadedEvent
| OpenFileInBrowserEvent
| AttachChatFileEvent
| OpenChatInActivePaneEvent
| SessionArchivedEvent
| ChatCreatedEvent
| ChatUpdatedEvent
| ChatArchivedEvent
| ChatUnarchivedEvent
| ChatDeletedEvent
| ProjectArchivedEvent
| ProjectUnarchivedEvent
| ProjectUpdatedEvent;
type Listener = (event: SessionEvent) => void;
const listeners = new Set<Listener>();
export const sessionEvents = {
emit(event: SessionEvent) {
for (const listener of listeners) {
try {
listener(event);
} catch {
// swallow — one bad listener shouldn't break others
}
}
},
subscribe(listener: Listener): () => void {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
},
};