Identified by automated analysis of ARTESCA-8467
Confidence: high
What needs to change
File: src/lib/components/tablev2/MultiSelectableContent.tsx
Fix the MultiSelectableContent.tsx in core-ui to properly separate single-row-click selection (for viewing details) from checkbox-based multi-selection (for actions like delete):
-
Clear single-selection state before checkbox multi-selection: In the row body onClick handler, instead of using toggleRowSelected(true) which writes to selectedRowIds, track the "viewed" row separately without polluting the multi-selection state. One approach:
onClick: onSingleRowSelected
? () => {
toggleAllRowsSelected(false); // clear multi-selection
onSingleRowSelected(row);
// Don't call row.toggleRowSelected(true) — this leaks into selectedRowIds
}
: ...
-
Fix the double-toggle on checkbox click: Prevent the checkbox's onChange from firing toggleRowSelected independently. Either:
- Add
event.stopPropagation() on the checkbox to prevent the onChange from bubbling, OR
- Remove the
toggleRowSelected call from handleMultipleSelectedRows (line 114) since the checkbox onChange already handles it, OR
- Override the checkbox's onChange to prevent the automatic toggle, letting
handleMultipleSelectedRows be the sole controller
-
Alternative approach in data-browser: If the core-ui fix is too risky, the data-browser could work around it by providing a getRowId prop to the Table that uniquely identifies each row (using Key + VersionId + type), and by clearing the single-selection state when switching to multi-select mode.
Root Cause
The root cause is in core-ui's MultiSelectableContent.tsx component, where single-row-click selection state leaks into checkbox-based multi-selection.
When onSingleRowSelected is provided (as it is in data-browser's ObjectList), two selection modes coexist:
-
Row body click (lines 135-140): Calls toggleAllRowsSelected(false) to clear all, then row.toggleRowSelected(true) to highlight the clicked row. This puts the row into react-table's internal selectedRowIds map.
-
Checkbox click (lines 169-176): Calls handleMultipleSelectedRows which reads selectedRowIds to determine which rows are already selected, then adds/removes the checkbox's row.
The bug occurs because the row-body click in step 1 writes to selectedRowIds (via toggleRowSelected(true)), and the checkbox click in step 2 reads from the same selectedRowIds. When a user clicks a version row to view its details, then clicks the delete marker's checkbox to select it for deletion, handleMultipleSelectedRows sees the version row in selectedRowIds and includes it in the multi-selection callback — even though the user only intended to select the delete marker.
Specifically in handleMultipleSelectedRows (lines 89-115):
const keys = Object.keys(selectedRowIds); // includes the previously-clicked version row!
onMultiSelectionChanged([
...rows.filter((row) => keys.includes(row.id)), // picks up the version row
rows[currentRowIndex], // adds the delete marker
]);
This results in both the delete marker AND the version appearing in the delete confirmation dialog, even though only the delete marker's checkbox was checked.
Additionally, there is a secondary double-toggle issue: clicking the checkbox fires both the checkbox's onChange (from getToggleRowSelectedProps) AND the container div's onClick (which calls handleMultipleSelectedRows → toggleRowSelected), causing toggleRowSelected to be called twice per click.
Evidence
src/lib/components/tablev2/MultiSelectableContent.tsx
├── L135: onClick: onSingleRowSelected ... — When the user clicks a row body (not the checkbox), the row is added to react-table's selectedRowIds via toggleRowSelected(true). This single-selection state persists and contaminates the next checkbox-based multi-selection.
├── L89: const handleMultipleSelectedRows = ( ... — handleMultipleSelectedRows reads selectedRowIds to determine existing selections. Since the previously single-clicked row is in selectedRowIds, it gets included in the multi-selection result via `rows.filter((row) => keys.includes(row.id))`, causing an unexpected extra row to appear as selected.
└── L163: if (cell.column.id === 'selection') { ... — The checkbox cell's div onClick calls handleMultipleSelectedRows. But the checkbox inside (rendered by useCheckbox) already has getToggleRowSelectedProps() onChange handler. Clicking the checkbox fires BOTH: the checkbox onChange (toggles row in react-table) and the div onClick (calls handleMultipleSelectedRows which also calls toggleRowSelected), causing a double-toggle of the selection state.
src/lib/components/tablev2/useCheckbox.tsx
└── L45: Cell: ({ row }) => { ... — The checkbox uses getToggleRowSelectedProps() which provides an onChange handler that calls toggleRowSelected. This onChange fires before the parent div's onClick handler, creating the double-toggle scenario.
Upstream Impact
Medium severity. When users browse versioned objects in the Data Browser and interact with delete markers, selecting a single delete marker for deletion incorrectly shows additional object versions in the delete confirmation dialog. This can lead to accidental deletion of object versions that the user did not intend to delete, especially problematic for objects under compliance retention lock. The issue affects all Data Browser users working with versioned S3 buckets containing delete markers.
What needs to change
File:
src/lib/components/tablev2/MultiSelectableContent.tsxFix the
MultiSelectableContent.tsxin core-ui to properly separate single-row-click selection (for viewing details) from checkbox-based multi-selection (for actions like delete):Clear single-selection state before checkbox multi-selection: In the row body onClick handler, instead of using
toggleRowSelected(true)which writes toselectedRowIds, track the "viewed" row separately without polluting the multi-selection state. One approach:Fix the double-toggle on checkbox click: Prevent the checkbox's
onChangefrom firingtoggleRowSelectedindependently. Either:event.stopPropagation()on the checkbox to prevent the onChange from bubbling, ORtoggleRowSelectedcall fromhandleMultipleSelectedRows(line 114) since the checkbox onChange already handles it, ORhandleMultipleSelectedRowsbe the sole controllerAlternative approach in data-browser: If the core-ui fix is too risky, the data-browser could work around it by providing a
getRowIdprop to the Table that uniquely identifies each row (usingKey + VersionId + type), and by clearing the single-selection state when switching to multi-select mode.Root Cause
The root cause is in core-ui's
MultiSelectableContent.tsxcomponent, where single-row-click selection state leaks into checkbox-based multi-selection.When
onSingleRowSelectedis provided (as it is in data-browser'sObjectList), two selection modes coexist:Row body click (lines 135-140): Calls
toggleAllRowsSelected(false)to clear all, thenrow.toggleRowSelected(true)to highlight the clicked row. This puts the row into react-table's internalselectedRowIdsmap.Checkbox click (lines 169-176): Calls
handleMultipleSelectedRowswhich readsselectedRowIdsto determine which rows are already selected, then adds/removes the checkbox's row.The bug occurs because the row-body click in step 1 writes to
selectedRowIds(viatoggleRowSelected(true)), and the checkbox click in step 2 reads from the sameselectedRowIds. When a user clicks a version row to view its details, then clicks the delete marker's checkbox to select it for deletion,handleMultipleSelectedRowssees the version row inselectedRowIdsand includes it in the multi-selection callback — even though the user only intended to select the delete marker.Specifically in
handleMultipleSelectedRows(lines 89-115):This results in both the delete marker AND the version appearing in the delete confirmation dialog, even though only the delete marker's checkbox was checked.
Additionally, there is a secondary double-toggle issue: clicking the checkbox fires both the checkbox's
onChange(fromgetToggleRowSelectedProps) AND the container div'sonClick(which callshandleMultipleSelectedRows→toggleRowSelected), causingtoggleRowSelectedto be called twice per click.Evidence
Upstream Impact
Medium severity. When users browse versioned objects in the Data Browser and interact with delete markers, selecting a single delete marker for deletion incorrectly shows additional object versions in the delete confirmation dialog. This can lead to accidental deletion of object versions that the user did not intend to delete, especially problematic for objects under compliance retention lock. The issue affects all Data Browser users working with versioned S3 buckets containing delete markers.