+
Added
Title
Type
@@ -17,8 +18,9 @@ export default function EntriesView({ entries, selectedEntryUid, onSelectEntry,
key={entry.entry_uid}
entry={entry}
archiveId={archiveId}
- isSelected={entry.entry_uid === selectedEntryUid}
- onSelect={() => onSelectEntry(entry)}
+ isSelected={selectedUids.size === 1 && selectedUids.has(entry.entry_uid)}
+ isMultiSelected={selectedUids.size >= 2 && selectedUids.has(entry.entry_uid)}
+ onRowClick={onRowClick}
/>
))}
diff --git a/frontend/src/components/EntryRow.jsx b/frontend/src/components/EntryRow.jsx
index 3d5eff3..57b6abf 100644
--- a/frontend/src/components/EntryRow.jsx
+++ b/frontend/src/components/EntryRow.jsx
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { formatTimestamp, formatBytes, valueText, sourceIconSvg } from '../utils';
-export default function EntryRow({ entry, archiveId, isSelected, onSelect }) {
+export default function EntryRow({ entry, archiveId, isSelected, isMultiSelected, onRowClick }) {
const [favFailed, setFavFailed] = useState(false);
const showFavicon =
entry.source_kind === 'web' &&
@@ -23,14 +23,33 @@ export default function EntryRow({ entry, archiveId, isSelected, onSelect }) {
);
+ const checked = isSelected || isMultiSelected;
+
+ function handleCheckboxClick(e) {
+ e.stopPropagation();
+ // treat checkbox tap as ctrl+click: toggle this entry without clearing others
+ onRowClick(entry, { ctrlKey: true, metaKey: false, shiftKey: false, preventDefault() {} });
+ }
+
return (
{ if (e.key === 'Enter') onSelect(); }}
+ onMouseDown={e => { if (e.shiftKey) e.preventDefault() }}
+ onClick={e => onRowClick(entry, e)}
+ onKeyDown={e => { if (e.key === 'Enter') onRowClick(entry, e) }}
>
+
+
{formatTimestamp(entry.archived_at)}
{icon}
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 715c6a8..e442556 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -306,6 +306,11 @@ select {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
+#entries-body > div.is-multi-selected {
+ background: #eee2d2;
+ outline: 2px solid var(--accent);
+ outline-offset: -2px;
+}
.col-added { width: 162px; color: var(--muted); }
.col-title { flex: 1 1 0; min-width: 0; overflow: hidden; display: flex; align-items: center; gap: 0.42em; }
@@ -322,8 +327,9 @@ select {
}
.col-url { flex: 0 0 30%; min-width: 0; overflow: hidden; }
-.entry-header-row > div:first-child,
-#entries-body > div > div:first-child { padding-left: 22px; }
+/* col-check is first-child but hidden on desktop — target col-added directly */
+.entry-header-row .col-added,
+#entries-body > div .col-added { padding-left: 22px; }
.entry-header-row > div:last-child,
#entries-body > div > div:last-child { padding-right: 22px; }
@@ -337,6 +343,56 @@ select {
#entries-body .is-selected .url-cell { overflow: visible; white-space: normal; }
.type-pill { display: inline-block; padding: 2px 6px; background: #d8e3df; color: #275a5f; border: 1px solid #bfd0ca; border-radius: var(--r); }
+/* ── Multi-select checkbox column ───────────────────────────────────────── */
+/* Hidden on desktop (pointer:fine); always visible on touch/mobile */
+.col-check {
+ display: none;
+ width: 40px;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: center;
+ padding: 0 !important;
+ cursor: pointer;
+}
+.row-checkbox {
+ /* reset