batch4: chats-in-sessions, force-send, /compact, right-rail file browser
Session 1:N Chat data model with backfill. Workspace switches to client-side multi-tab pane management. Right-rail file browser with float-over viewer and click-drag line selection replaces FileBrowserPane. Adds /compact streaming summarizer (respects compact markers in context builder), force-send (cancels in-flight, persists partial as 'cancelled', awaits cancellation completion via deferred Promise + 5s timeout), message queue, stop generation, chat auto-rename, session archive/unarchive with Closed Sessions section on repo landing page. CHECK constraints on sessions.status, messages.role, messages.status with KEEP IN SYNC comments tying to MESSAGE_ROLES / MESSAGE_STATUSES const arrays. Deletes dead pane routes/hook and the api.panes.* client block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -140,26 +140,50 @@ function applyEvent(prev: SidebarResponse, event: import('./sessionEvents').Sess
|
||||
case 'open_file_in_browser':
|
||||
// Consumed by Workspace (T7); no sidebar state change needed.
|
||||
return prev;
|
||||
case 'attach_chat_file':
|
||||
return prev;
|
||||
case 'session_archived': {
|
||||
let changed = false;
|
||||
const projects = prev.projects.map((p) => {
|
||||
if (p.id !== event.project_id) return p;
|
||||
const recent = p.recent_sessions.filter((s) => s.id !== event.session_id);
|
||||
if (recent.length === p.recent_sessions.length) return p;
|
||||
changed = true;
|
||||
return {
|
||||
...p,
|
||||
recent_sessions: recent,
|
||||
total_sessions: Math.max(0, p.total_sessions - 1),
|
||||
};
|
||||
});
|
||||
return changed ? { ...prev, projects } : prev;
|
||||
}
|
||||
case 'chat_created':
|
||||
case 'chat_updated':
|
||||
case 'chat_closed':
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
|
||||
// One bus subscription for the lifetime of the module. Events arriving
|
||||
// before the initial fetch resolves are dropped; the eventual fetch
|
||||
// result is the source of truth.
|
||||
sessionEvents.subscribe((event) => {
|
||||
// session_loaded updates activeSessionProjectId regardless of whether
|
||||
// sharedData is populated yet — notify so subscribers can re-read.
|
||||
if (event.type === 'session_loaded') {
|
||||
activeSession = { session_id: event.session_id, project_id: event.project_id };
|
||||
// Guard prevents duplicate listeners during Vite HMR reloads.
|
||||
const G = globalThis as Record<string, unknown>;
|
||||
if (!G.__boocode_sidebar_subscribed) {
|
||||
G.__boocode_sidebar_subscribed = true;
|
||||
sessionEvents.subscribe((event) => {
|
||||
if (event.type === 'session_loaded') {
|
||||
activeSession = { session_id: event.session_id, project_id: event.project_id };
|
||||
notify();
|
||||
return;
|
||||
}
|
||||
if (!sharedData) return;
|
||||
const next = applyEvent(sharedData, event);
|
||||
if (next === sharedData) return;
|
||||
sharedData = next;
|
||||
notify();
|
||||
return;
|
||||
}
|
||||
if (!sharedData) return;
|
||||
const next = applyEvent(sharedData, event);
|
||||
if (next === sharedData) return;
|
||||
sharedData = next;
|
||||
notify();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
interface Snapshot {
|
||||
data: SidebarResponse | null;
|
||||
|
||||
Reference in New Issue
Block a user