1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-22 03:05:32 +02:00

fix(core+frontend): child entry selection and delete correctness

database.rs — delete_entry:
- subtree_ids now uses WHERE id = ?1 OR root_entry_id = ?1 so the entry
  itself is always included; previously a child deletion passed an empty
  vec to cascade_cached_bytes_after_subtree_delete (no grandchildren
  exist), leaving cached_bytes stale on entries sharing that child's blobs

App.jsx — handleRowClick:
- Shift-range now queries DOM order (#entries-body [data-entry-uid])
  instead of entries.findIndex(); child rows are in the DOM but not in
  the root entries array, so findIndex always returned -1 for them
- Ctrl/meta branch computes next set before the state update so
  selectEntry can fire synchronously for child rows; auto-snap only
  restores root entries, so ctrl-clicking a lone child never loaded its
  detail panel — now calls selectEntry(entry) when the child ends up as
  the sole selection, selectEntry(null) on multi or deselect
- selectedUids added to handleRowClick's useCallback dep array
This commit is contained in:
TheGeneralist 2026-07-21 13:39:35 +02:00
parent d07e4d7ac9
commit 03406099de
Signed by: thegeneralist01
SSH key fingerprint: SHA256:pp9qddbCNmVNoSjevdvQvM5z0DHN7LTa8qBMbcMq/R4
4 changed files with 34 additions and 25 deletions

View file

@ -262,10 +262,13 @@ export default function App() {
const handleRowClick = useCallback((entry, e) => {
if (e.shiftKey && lastAnchorIndexRef.current !== null) {
e.preventDefault()
const anchorIdx = entries.findIndex(x => x.entry_uid === lastAnchorIndexRef.current)
const clickIdx = entries.findIndex(x => x.entry_uid === entry.entry_uid)
// Use DOM order so child rows (not in the `entries` array) participate
// in range selection. Every rendered row has data-entry-uid.
const allNodes = [...document.querySelectorAll('#entries-body [data-entry-uid]')]
const anchorIdx = allNodes.findIndex(n => n.dataset.entryUid === lastAnchorIndexRef.current)
const clickIdx = allNodes.findIndex(n => n.dataset.entryUid === entry.entry_uid)
if (anchorIdx === -1 || clickIdx === -1) {
// anchor evicted by search/filter/delete fall back to single select
// anchor no longer visible fall back to single select
lastAnchorIndexRef.current = entry.entry_uid
setSelectedUids(new Set([entry.entry_uid]))
selectEntry(entry)
@ -273,24 +276,25 @@ export default function App() {
}
const lo = Math.min(anchorIdx, clickIdx)
const hi = Math.max(anchorIdx, clickIdx)
const range = entries.slice(lo, hi + 1)
const uids = new Set(range.map(x => x.entry_uid))
const uids = new Set(allNodes.slice(lo, hi + 1).map(n => n.dataset.entryUid))
setSelectedUids(uids)
if (uids.size === 1) selectEntry(range[0])
if (uids.size === 1) selectEntry(entry)
} else if (e.ctrlKey || e.metaKey) {
lastAnchorIndexRef.current = entry.entry_uid
setSelectedUids(prev => {
const next = new Set(prev)
if (next.has(entry.entry_uid)) next.delete(entry.entry_uid)
else next.add(entry.entry_uid)
return next
})
const next = new Set(selectedUids)
if (next.has(entry.entry_uid)) next.delete(entry.entry_uid)
else next.add(entry.entry_uid)
setSelectedUids(next)
// Auto-snap only restores root entries; explicitly drive selectEntry so
// ctrl-clicking a single child row loads its detail and clears on multi.
if (next.size === 1 && next.has(entry.entry_uid)) selectEntry(entry)
else if (next.size !== 1) selectEntry(null)
} else {
lastAnchorIndexRef.current = entry.entry_uid
setSelectedUids(new Set([entry.entry_uid]))
selectEntry(entry)
}
}, [entries, selectEntry])
}, [entries, selectedUids, selectEntry])
const handleTagFilterSet = useCallback((fullPath) => {
setTagFilter(fullPath)