Download history refactor pt.2 (#703)

Two-phase download history: downloads are now recorded in the DB at
queue time (not just at terminal time), eliminating the need to
reconstruct metadata in the terminal hook and removing the
`_is_graduated_request_download()` request-scan mess
This commit is contained in:
Alex
2026-03-05 13:06:34 +00:00
committed by GitHub
parent fbe25725d3
commit cce2c10704
22 changed files with 776 additions and 570 deletions
+19 -6
View File
@@ -299,16 +299,29 @@ function App() {
}, [currentStatus, dismissedDownloadTaskIds]);
// Use real-time buckets for active work and merge persisted terminal buckets
// so completed/errored entries survive restarts.
const activitySidebarStatus = useMemo<StatusData>(() => ({
// so completed/errored entries survive restarts. Filter out dismissed items
// so the sidebar counts stay consistent with the activity panel.
const activitySidebarStatus = useMemo<StatusData>(() => {
const filterDismissed = (
bucket: Record<string, Book> | undefined
): Record<string, Book> | undefined => {
if (!bucket || dismissedDownloadTaskIds.size === 0) return bucket;
const filtered = Object.fromEntries(
Object.entries(bucket).filter(([taskId]) => !dismissedDownloadTaskIds.has(taskId))
) as Record<string, Book>;
return Object.keys(filtered).length > 0 ? filtered : undefined;
};
return {
queued: currentStatus.queued,
resolving: currentStatus.resolving,
locating: currentStatus.locating,
downloading: currentStatus.downloading,
complete: mergeTerminalBucket(activityStatus.complete, currentStatus.complete),
error: mergeTerminalBucket(activityStatus.error, currentStatus.error),
cancelled: mergeTerminalBucket(activityStatus.cancelled, currentStatus.cancelled),
}), [activityStatus, currentStatus]);
complete: filterDismissed(mergeTerminalBucket(activityStatus.complete, currentStatus.complete)),
error: filterDismissed(mergeTerminalBucket(activityStatus.error, currentStatus.error)),
cancelled: filterDismissed(mergeTerminalBucket(activityStatus.cancelled, currentStatus.cancelled)),
};
}, [activityStatus, currentStatus, dismissedDownloadTaskIds]);
const showRequestsTab = useMemo(() => {
if (requestRoleIsAdmin) {
@@ -175,6 +175,19 @@ const buildActions = (item: ActivityItem, isAdmin: boolean): ActivityCardAction[
},
];
}
if (item.visualStatus === 'cancelled') {
return [
{
kind: 'download-retry',
bookId: item.downloadBookId,
},
{
kind: 'download-dismiss',
bookId: item.downloadBookId,
linkedRequestId: item.requestId,
},
];
}
return [
{
kind: 'download-dismiss',
+1
View File
@@ -407,6 +407,7 @@ export const useActivity = ({
void clearActivityHistory()
.then(() => {
void refreshActivitySnapshot();
void refreshActivityHistory();
})
.catch((error) => {
console.error('Clear history failed:', error);
+1 -1
View File
@@ -430,7 +430,7 @@ export interface ActivityDismissPayload {
}
export interface ActivityHistoryItem {
id: number;
id: string;
user_id: number;
item_type: 'download' | 'request';
item_key: string;
+1 -1
View File
@@ -207,7 +207,7 @@ export interface RequestRecord {
id: number;
user_id: number;
status: 'pending' | 'fulfilled' | 'rejected' | 'cancelled';
delivery_state?: 'none' | 'unknown' | 'queued' | 'resolving' | 'locating' | 'downloading' | 'complete' | 'error' | 'cancelled';
delivery_state?: 'none' | 'queued' | 'resolving' | 'locating' | 'downloading' | 'complete' | 'error' | 'cancelled';
delivery_updated_at?: string | null;
last_failure_reason?: string | null;
source_hint: string | null;