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:
2026-05-15 20:39:48 +00:00
parent 6d9515b8a5
commit c35ec65fc4
37 changed files with 3290 additions and 1012 deletions

View File

@@ -0,0 +1,44 @@
export type Attachment = {
id: string;
kind: 'file' | 'lines' | 'paste';
filename: string;
language: string | null;
content: string;
range?: [number, number];
source: '@' | 'line-select' | 'drop' | 'paste';
};
export const LANG_MAP: Record<string, string> = {
ts: 'typescript', tsx: 'tsx', js: 'javascript', jsx: 'jsx',
mjs: 'javascript', cjs: 'javascript',
py: 'python', go: 'go', rs: 'rust', rb: 'ruby', java: 'java',
c: 'c', h: 'c', cpp: 'cpp', cc: 'cpp', hpp: 'cpp', cs: 'csharp',
php: 'php', sh: 'bash', bash: 'bash', zsh: 'bash',
yml: 'yaml', yaml: 'yaml', json: 'json', toml: 'toml',
md: 'markdown', markdown: 'markdown', sql: 'sql', dockerfile: 'dockerfile',
html: 'html', htm: 'html', css: 'css', scss: 'scss',
};
export function inferLanguage(filename: string): string | null {
const base = filename.split('/').pop() ?? filename;
if (base.toLowerCase() === 'dockerfile') return 'dockerfile';
const m = base.match(/\.([^.]+)$/);
return m ? (LANG_MAP[m[1]!.toLowerCase()] ?? null) : null;
}
export function flattenToMessage(attachments: Attachment[], text: string): string {
if (attachments.length === 0) return text;
const blocks = attachments.map(a => {
const fence = '```' + (a.language ?? '');
let header: string;
if (a.kind === 'lines') {
header = `// from: ${a.filename}:${a.range?.[0] ?? '?'}-${a.range?.[1] ?? '?'}`;
} else if (a.kind === 'paste') {
header = `// from: pasted text (${a.content.split('\n').length} lines)`;
} else {
header = `// from: ${a.filename}`;
}
return `${fence}\n${header}\n${a.content}\n\`\`\``;
});
return [...blocks, text].filter(Boolean).join('\n\n');
}