diff --git a/frontend/src/DesktopWorkspace.css b/frontend/src/DesktopWorkspace.css
index 4aecad2..835c4f6 100644
--- a/frontend/src/DesktopWorkspace.css
+++ b/frontend/src/DesktopWorkspace.css
@@ -132,8 +132,7 @@
display: inline-flex;
align-items: center;
overflow: hidden;
- box-shadow: 2px 2px 4px var(--shadow-faint);
- background: color-mix(in oklch, var(--surface-subtle) 88%, transparent);
+ background: color-mix(in oklch, var(--surface-subtle) 90%, transparent);
color: var(--muted);
}
@@ -150,7 +149,6 @@
pointer-events: auto;
cursor: grab;
transition: transform 0.16s ease, opacity 0.2s ease, box-shadow 0.2s ease;
- box-shadow: 2px 2px 4px var(--shadow-medium);
}
.desk-help-overlay {
diff --git a/frontend/src/DesktopWorkspace.jsx b/frontend/src/DesktopWorkspace.jsx
index a811c68..db24a3c 100644
--- a/frontend/src/DesktopWorkspace.jsx
+++ b/frontend/src/DesktopWorkspace.jsx
@@ -9,7 +9,7 @@ import React, {
import { createPortal } from 'react-dom';
import { resolveDocumentAssetUrl, createAssetView } from './asset_manager';
import { useAssetNavigator } from './hooks/useAssetNavigator';
-import { ArrowLeftIcon, ArrowRightIcon, CloseIcon, FileIcon, FolderOutlineIcon } from './ui/icons';
+import { ArrowLeftIcon, ArrowRightIcon, CloseIcon } from './ui/icons';
import SelectionFloatingActions from './documents/SelectionFloatingActions';
import { createDocumentsTableHeaderActions } from './documents/DocumentsPanel';
import createWorkspaceSurfaceConfig from './documents/workspaceHeader';
@@ -590,6 +590,7 @@ const DesktopWorkspace = ({
}) => {
const items = useMemo(() => (searchResults ? searchResults : documents), [documents, searchResults]);
+ const allowLayoutPersistence = Boolean(tenantId && viewId && viewId.startsWith('folder:'));
const containerRef = useRef(null);
const layoutRef = useRef(new Map());
@@ -1120,7 +1121,7 @@ const recalcVisibleDocIds = useCallback(() => {
const persistLayoutSnapshot = useCallback(
async (snapshot, force = false) => {
- if (!tenantId || !viewId) {
+ if (!allowLayoutPersistence || !tenantId || !viewId) {
return;
}
if (!force && !layoutDirtyRef.current) {
@@ -1160,7 +1161,7 @@ const recalcVisibleDocIds = useCallback(() => {
await upsertLayoutRecords({ tenantId, viewId, entries: records });
},
- [tenantId, viewId],
+ [tenantId, viewId, allowLayoutPersistence],
);
const syncLayoutSnapshot = useCallback((force = false) => {
@@ -1207,6 +1208,11 @@ const recalcVisibleDocIds = useCallback(() => {
return () => observer.disconnect();
}, []);
useEffect(() => {
+ if (!allowLayoutPersistence) {
+ persistedLayoutRef.current = new Map();
+ layoutDirtyRef.current = false;
+ return;
+ }
if (!tenantId || !viewId) {
return;
}
@@ -1245,7 +1251,7 @@ const recalcVisibleDocIds = useCallback(() => {
return () => {
cancelled = true;
};
- }, [tenantId, viewId]);
+ }, [tenantId, viewId, allowLayoutPersistence]);
useLayoutEffect(() => {
if (!containerRef.current || !canvasSize.width || !canvasSize.height) {
@@ -2496,6 +2502,7 @@ export const createDesktopSurface = ({
selectedDocumentIds,
selectedFolderIds,
onDeleteSelection,
+ onClearSelection,
tags,
correspondents,
documentLookup,
@@ -2506,6 +2513,7 @@ export const createDesktopSurface = ({
onBulkCorrespondentRemove,
onBulkReanalyze,
} = workspaceProps;
+
const title = Array.isArray(searchResults) ? 'Search results' : currentFolderName;
const subtitle = Array.isArray(searchResults)
? `${searchResults.length} matching document${searchResults.length === 1 ? '' : 's'}`
@@ -2515,46 +2523,6 @@ export const createDesktopSurface = ({
const documentSelectionCount = Array.isArray(selectedDocumentIds) ? selectedDocumentIds.length : 0;
const folderSelectionCount = Array.isArray(selectedFolderIds) ? selectedFolderIds.length : 0;
const selectionCount = documentSelectionCount + folderSelectionCount;
- let selectionLabel = null;
- if (selectionCount) {
- const tokens = [];
-
- if (documentSelectionCount) {
- tokens.push({
- key: 'documents',
- count: documentSelectionCount,
- icon: ,
- });
- }
-
- if (folderSelectionCount) {
- tokens.push({
- key: 'folders',
- count: folderSelectionCount,
- icon: ,
- });
- }
-
- if (tokens.length) {
- selectionLabel = (
-
- {tokens.map((token, index) => (
-
- {index > 0 ? · : null}
-
- {token.count}
- ×
- {token.icon}
-
-
- ))}
-
- );
- } else {
- selectionLabel = `${selectionCount} item${selectionCount === 1 ? '' : 's'}`;
- }
- }
-
const actions = createDocumentsTableHeaderActions({
viewMode: viewMode || 'desk',
onViewModeChange,
@@ -2562,11 +2530,12 @@ export const createDesktopSurface = ({
onShowDeskHelp: viewMode === 'desk' ? workspaceProps?.onOpenHelp : null,
});
- const floatingActions = selectionLabel
+ const floatingActions = selectionCount > 0
? (
)
: null;
@@ -2593,7 +2563,7 @@ export const createDesktopSurface = ({
onNavigateParent,
actions,
breadcrumbs: workspaceProps?.breadcrumbs || null,
- selectionLabel,
+ selectionLabel: null,
floatingActions,
content: ,
detail,
diff --git a/frontend/src/documents/DocumentsPanel.jsx b/frontend/src/documents/DocumentsPanel.jsx
index 09a7867..9eaefb7 100644
--- a/frontend/src/documents/DocumentsPanel.jsx
+++ b/frontend/src/documents/DocumentsPanel.jsx
@@ -6,8 +6,6 @@ import {
RefreshIcon,
MinusVerticalIcon,
InfoIcon,
- FileIcon,
- FolderOutlineIcon,
} from '../ui/icons';
import BreadcrumbTrail from '../ui/BreadcrumbTrail';
import createWorkspaceSurfaceConfig from './workspaceHeader';
@@ -609,6 +607,7 @@ export const createDocumentsSurface = ({
selectedDocumentIds,
selectedFolderIds,
onDeleteSelection,
+ onClearSelection,
tags,
correspondents,
documentLookup,
@@ -629,45 +628,6 @@ export const createDocumentsSurface = ({
const documentSelectionCount = Array.isArray(selectedDocumentIds) ? selectedDocumentIds.length : 0;
const folderSelectionCount = Array.isArray(selectedFolderIds) ? selectedFolderIds.length : 0;
const selectionCount = documentSelectionCount + folderSelectionCount;
- let selectionLabel = null;
- if (selectionCount) {
- const tokens = [];
-
- if (documentSelectionCount) {
- tokens.push({
- key: 'documents',
- count: documentSelectionCount,
- icon: ,
- });
- }
-
- if (folderSelectionCount) {
- tokens.push({
- key: 'folders',
- count: folderSelectionCount,
- icon: ,
- });
- }
-
- if (tokens.length) {
- selectionLabel = (
-
- {tokens.map((token, index) => (
-
- {index > 0 ? · : null}
-
- {token.count}
- ×
- {token.icon}
-
-
- ))}
-
- );
- } else {
- selectionLabel = `${selectionCount} item${selectionCount === 1 ? '' : 's'}`;
- }
- }
const actions = createDocumentsTableHeaderActions({
viewMode,
@@ -675,11 +635,12 @@ export const createDocumentsSurface = ({
onRefresh,
});
- const floatingActions = selectionLabel
+ const floatingActions = selectionCount > 0
? (
)
: null;
@@ -708,7 +670,7 @@ export const createDocumentsSurface = ({
onNavigateParent,
actions,
breadcrumbs,
- selectionLabel,
+ selectionLabel: null,
floatingActions,
content: ,
detail,
diff --git a/frontend/src/documents/SelectionFloatingActions.jsx b/frontend/src/documents/SelectionFloatingActions.jsx
index c399eed..3e72cdb 100644
--- a/frontend/src/documents/SelectionFloatingActions.jsx
+++ b/frontend/src/documents/SelectionFloatingActions.jsx
@@ -1,6 +1,7 @@
import React, { useCallback, useMemo } from 'react';
-import { TrashIcon, AnalyzeIcon } from '../ui/icons';
+import { TrashIcon, AnalyzeIcon, IconX } from '../ui/icons';
import SelectionAssignmentMenu from './SelectionAssignmentMenu';
+import SelectionSummary from './SelectionSummary';
const normalizeDocumentList = (selectedDocumentIds) =>
Array.isArray(selectedDocumentIds) ? selectedDocumentIds.filter(Boolean) : [];
@@ -112,6 +113,7 @@ const buildCorrespondentAssignments = (selectedDocuments, correspondents, total)
const SelectionFloatingActions = ({
selectionCount = 0,
selectedDocumentIds,
+ selectedFolderIds = [],
documentLookup,
tags,
tagLookupById,
@@ -122,12 +124,24 @@ const SelectionFloatingActions = ({
onBulkCorrespondentRemove,
onBulkReanalyze,
onDeleteSelection,
+ onClearSelection = null,
}) => {
const documentIdList = useMemo(
() => normalizeDocumentList(selectedDocumentIds),
[selectedDocumentIds],
);
+ const folderIdList = useMemo(
+ () => normalizeDocumentList(selectedFolderIds),
+ [selectedFolderIds],
+ );
+
+ const documentCount = documentIdList.length;
+ const folderCount = folderIdList.length;
+ const totalCount = typeof selectionCount === 'number'
+ ? selectionCount
+ : documentCount + folderCount;
+
const selectedDocuments = useMemo(() => {
if (!documentIdList.length || !(documentLookup instanceof Map)) {
return [];
@@ -203,52 +217,77 @@ const SelectionFloatingActions = ({
[selectedDocCount, onBulkCorrespondentAdd, documentIdList],
);
+ const summaryNode = totalCount > 0 ? (
+
+ ) : null;
+
return (
-
-
-
- {typeof onBulkReanalyze === 'function' ? (
-
+ <>
+ {summaryNode ? (
+
{summaryNode}
) : null}
- {typeof onDeleteSelection === 'function' ? (
-
- ) : null}
-
+
+
+
+ {typeof onBulkReanalyze === 'function' ? (
+
+ ) : null}
+ {typeof onDeleteSelection === 'function' ? (
+
+ ) : null}
+ {typeof onClearSelection === 'function' ? (
+
+ ) : null}
+
+ >
);
};
diff --git a/frontend/src/documents/SelectionSummary.jsx b/frontend/src/documents/SelectionSummary.jsx
new file mode 100644
index 0000000..6505bbc
--- /dev/null
+++ b/frontend/src/documents/SelectionSummary.jsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import { FileIcon, FolderOutlineIcon } from '../ui/icons';
+
+const SelectionSummary = ({ documentCount = 0, folderCount = 0, totalCount = 0 }) => {
+ const docCount = Number(documentCount) || 0;
+ const folderCountNumber = Number(folderCount) || 0;
+ const aggregateCount = docCount + folderCountNumber;
+ const resolvedTotal = Number(totalCount) || aggregateCount;
+
+ if (!docCount && !folderCountNumber && !resolvedTotal) {
+ return null;
+ }
+
+ const tokens = [];
+
+ if (docCount) {
+ tokens.push({
+ key: 'documents',
+ count: docCount,
+ icon: ,
+ });
+ }
+
+ if (folderCountNumber) {
+ tokens.push({
+ key: 'folders',
+ count: folderCountNumber,
+ icon: ,
+ });
+ }
+
+ if (!tokens.length) {
+ const count = resolvedTotal;
+ return (
+
+ {`${count} item${count === 1 ? '' : 's'}`}
+
+ );
+ }
+
+ return (
+
+ {tokens.map((token, index) => (
+
+ {index > 0 ? · : null}
+
+ {token.count}
+ {token.icon}
+
+
+ ))}
+
+ );
+};
+
+export default SelectionSummary;
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 2dba138..e527fe4 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -2076,10 +2076,8 @@ button.danger:hover:not([disabled]) {
height: 1rem;
}
-.selection-summary__multiply {
- display: inline-flex;
- align-items: center;
- opacity: 0.6;
+.selection-summary--text {
+ font-weight: 600;
}
.selection-summary__separator {