memory isolation fix and session grouping in client

This commit is contained in:
Storme-bit
2026-04-19 02:09:12 -07:00
parent 56355d232b
commit 9c903a56ae
7 changed files with 238 additions and 96 deletions

View File

@@ -45,11 +45,6 @@ export default function Sidebar({
}
}
function getPreview(session) {
if (session.isNew) return 'New conversation';
return session.name || session.external_id;
}
// ── Collapsed rail ───────────────────────────────────────
if (!isOpen) {
@@ -96,6 +91,30 @@ export default function Sidebar({
const recentSessions = sessions.slice(0, 10);
// Group recent sessions by project
const grouped = {};
const unassigned = [];
for (const session of recentSessions) {
if (session.project_id) {
if (!grouped[session.project_id]) grouped[session.project_id] = [];
grouped[session.project_id].push(session);
} else {
unassigned.push(session);
}
}
const sessionRowProps = (session) => ({
key: session.external_id,
session,
isActive: activeSession?.external_id === session.external_id,
isHovered: hoveredId === session.external_id,
onHover: setHoveredId,
onSelect: () => { onSelectSession(session); onNavigate('chat'); },
onRename: () => { setModalMode('settings'); setModalSession(session); },
onDelete: () => { setModalMode('confirm-delete'); setModalSession(session); },
onContextMenu: e => !session.isNew && openMenu(e, session),
});
return (
<>
<div className="flex-col" style={{
@@ -141,45 +160,45 @@ export default function Sidebar({
isOpen={projectsOpen}
onToggle={() => setProjectsOpen(o => !o)}
/>
{projectsOpen && (
<div style={{ padding: '4px 10px 8px' }}>
{!projects?.length ? (
<div style={{
padding: '10px',
borderRadius: 'var(--radius-md)',
border: '1px dashed var(--border)',
color: 'var(--text-muted)',
fontSize: '12px',
textAlign: 'center',
}}>
No projects yet
</div>
) : (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
{projects.slice(0, 6).map(project => (
<button
key={project.id}
onClick={() => {onSelectProject(project); onNavigate('project')}}
className="btn-reset text-xs"
style={{
padding: '4px 8px',
borderRadius: 'var(--radius-sm)',
background: 'var(--bg-elevated)',
border: `1px solid ${project.colour ?? 'var(--border)'}`,
color: 'var(--text-secondary)',
maxWidth: '100%',
}}
title={project.description ?? project.name}
>
<span className="truncate" style={{ display: 'block', maxWidth: '140px' }}>
{project.name}
</span>
</button>
))}
</div>
)}
</div>
)}
{projectsOpen && (
<div style={{ padding: '4px 10px 8px' }}>
{!projects?.length ? (
<div style={{
padding: '10px',
borderRadius: 'var(--radius-md)',
border: '1px dashed var(--border)',
color: 'var(--text-muted)',
fontSize: '12px',
textAlign: 'center',
}}>
No projects yet
</div>
) : (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
{projects.slice(0, 6).map(project => (
<button
key={project.id}
onClick={() => { onSelectProject(project); onNavigate('project'); }}
className="btn-reset text-xs"
style={{
padding: '4px 8px',
borderRadius: 'var(--radius-sm)',
background: 'var(--bg-elevated)',
border: `1px solid ${project.colour ?? 'var(--border)'}`,
color: 'var(--text-secondary)',
maxWidth: '100%',
}}
title={project.description ?? project.name}
>
<span className="truncate" style={{ display: 'block', maxWidth: '140px' }}>
{project.name}
</span>
</button>
))}
</div>
)}
</div>
)}
<div style={{ height: '1px', background: 'var(--border)', margin: '2px 0' }} />
@@ -189,28 +208,54 @@ export default function Sidebar({
isOpen={chatsOpen}
onToggle={() => setChatsOpen(o => !o)}
/>
{chatsOpen && (
<>
{recentSessions.map(session => (
<SessionRow
key={session.external_id}
session={session}
isActive={activeSession?.external_id === session.external_id}
isHovered={hoveredId === session.external_id}
onHover={setHoveredId}
onSelect={() => { onSelectSession(session); onNavigate('chat'); }}
onRename={() => { setModalMode('settings'); setModalSession(session); }}
onDelete={() => { setModalMode('confirm-delete'); setModalSession(session); }}
onContextMenu={e => !session.isNew && openMenu(e, session)}
/>
))}
{recentSessions.length === 0 && (
<div className="text-xs text-muted" style={{ padding: '12px 16px', textAlign: 'center' }}>
No conversations yet
</div>
)}
{/* Project groups */}
{Object.entries(grouped).map(([projectId, projectSessions]) => {
const project = projects?.find(p => p.id === Number(projectId));
return (
<div key={projectId}>
{/* Project group label */}
<div style={{
display: 'flex', alignItems: 'center', gap: '6px',
padding: '6px 16px 2px',
}}>
<div style={{
width: '6px', height: '6px', borderRadius: '50%', flexShrink: 0,
background: project?.colour ?? 'var(--accent)',
}} />
<span className="text-xs text-muted truncate">
{project?.name ?? 'Project'}
</span>
</div>
{projectSessions.map(session => (
<SessionRow {...sessionRowProps(session)} />
))}
</div>
);
})}
{/* Unassigned sessions */}
{unassigned.length > 0 && (
<>
{Object.keys(grouped).length > 0 && (
<div style={{ padding: '6px 16px 2px' }}>
<span className="text-xs text-muted">Other</span>
</div>
)}
{unassigned.map(session => (
<SessionRow {...sessionRowProps(session)} />
))}
</>
)}
{sessions.length > 0 && (
<button
onClick={() => onNavigate('all-chats')}