+ {/* Pending uploaded files */}
+ {pendingFiles.length > 0 && (
+
+ {pendingFiles.map(pf => (
+
+ 📎 {pf.fileName.length > 25 ? pf.fileName.slice(0, 25) + '...' : pf.fileName}
+ {onRemovePendingFile && (
+
+ )}
+
+ ))}
+
+ )}
+
+ {/* Attachment bar */}
+ {hasAttachments && (
+
+ {attachedFileIds.map(fId => {
+ const file = files.find(f => f.id === fId);
+ return (
+
+ 📄 {file?.fileName || fId}
+
+
+ );
+ })}
+ {attachedDataSourceIds.map(dsId => {
+ const ds = dataSources.find(d => d.id === dsId);
+ return (
+
+ 🔗 {ds?.label || dsId}
+
+
+ );
+ })}
+
+ )}
+
+ {/* Autocomplete dropdown */}
+ {showAutocomplete && filteredFiles.length > 0 && (
+
+ {filteredFiles.slice(0, 10).map(f => (
+
_insertFileRef(f.fileName)}
+ style={{
+ padding: '8px 12px',
+ cursor: 'pointer',
+ fontSize: 13,
+ borderBottom: '1px solid #f0f0f0',
+ }}
+ onMouseEnter={e => (e.currentTarget.style.background = '#f5f5f5')}
+ onMouseLeave={e => (e.currentTarget.style.background = '')}
+ >
+ @{f.fileName}
+
+ {f.mimeType} · {(f.fileSize / 1024).toFixed(1)}KB
+
+
+ ))}
+
+ )}
+
+ {/* Main input row */}
+
+
+
+
+
+ {onProvidersChange && (
+
+ )}
+
+
+
+ {isProcessing ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
diff --git a/src/pages/views/workspace/WorkspaceKeepAlive.tsx b/src/pages/views/workspace/WorkspaceKeepAlive.tsx
new file mode 100644
index 0000000..3202883
--- /dev/null
+++ b/src/pages/views/workspace/WorkspaceKeepAlive.tsx
@@ -0,0 +1,48 @@
+/**
+ * WorkspaceKeepAlive
+ *
+ * Renders the WorkspacePage permanently at the MainLayout level so it
+ * survives route changes. Visibility is toggled via CSS `display`
+ * instead of mount / unmount, preserving messages, SSE connections,
+ * files, and all other workspace state.
+ */
+
+import React, { useRef } from 'react';
+import { useLocation } from 'react-router-dom';
+import { WorkspacePage } from './WorkspacePage';
+
+const _WORKSPACE_ROUTE_RE = /\/mandates\/([^/]+)\/workspace\/([^/]+)/;
+
+interface WorkspaceKeepAliveProps {
+ isVisible: boolean;
+}
+
+export const WorkspaceKeepAlive: React.FC