Fix state persistence when popup is closed

- Add 'dismissed' status to prevent dismissed PDFs from reappearing
- Persist PDFs discovered from current tab and recent downloads via background
- Add dismissPDF function that marks PDFs as dismissed instead of removing
- Dismissed PDFs are kept for 7 days for duplicate detection, then cleaned up
- Completed items (sent/canceled) can still be fully removed
- Add addPDF message handler to service worker for popup-discovered PDFs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-16 10:20:28 +01:00
parent 3a48d4f497
commit 468473f03b
3 changed files with 89 additions and 27 deletions

View File

@@ -11,6 +11,7 @@ import {
getAllPDFs, getAllPDFs,
getPendingPDFs, getPendingPDFs,
updatePDFStatus, updatePDFStatus,
dismissPDF,
removePDF, removePDF,
cleanupOldEntries, cleanupOldEntries,
PDFStatus, PDFStatus,
@@ -129,6 +130,19 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
return true; return true;
} }
// Add a PDF to the queue (from popup discovery)
if (request.action === 'addPDF') {
addPDF(request.pdf).then(entry => {
if (entry) {
console.log('[Service Worker] PDF added via message:', entry.filename);
}
return updateBadge().then(() => entry);
}).then(entry => {
sendResponse({ entry });
});
return true;
}
// Legacy: Get only actionable PDFs // Legacy: Get only actionable PDFs
if (request.action === 'getPDFQueue') { if (request.action === 'getPDFQueue') {
getPendingPDFs().then(entries => { getPendingPDFs().then(entries => {
@@ -148,6 +162,15 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
return true; return true;
} }
if (request.action === 'dismissPDF') {
dismissPDF(request.id).then(() => {
return updateBadge();
}).then(() => {
sendResponse({ success: true });
});
return true;
}
if (request.action === 'removePDF') { if (request.action === 'removePDF') {
removePDF(request.id).then(() => { removePDF(request.id).then(() => {
return updateBadge(); return updateBadge();

View File

@@ -153,22 +153,20 @@ async function loadPDFQueue() {
console.log('[Popup] Loading PDF queue...'); console.log('[Popup] Loading PDF queue...');
// Get all PDFs from background script (including completed ones) // Get all PDFs from background script (including completed ones)
const response = await chrome.runtime.sendMessage({ action: 'getAllPDFs' }); let response = await chrome.runtime.sendMessage({ action: 'getAllPDFs' });
pdfQueue = response?.entries || []; pdfQueue = response?.entries || [];
console.log('[Popup] Got', pdfQueue.length, 'entries from background'); console.log('[Popup] Got', pdfQueue.length, 'entries from background');
// Also check current tab for PDF // Check current tab for PDF and add to persistent queue via background
const currentTabPDF = await checkCurrentTabForPDF(); const currentTabPDF = await checkCurrentTabForPDF();
if (currentTabPDF) { if (currentTabPDF) {
// Check if already in queue // Add via background service (will check for duplicates/dismissed)
const exists = pdfQueue.some(p => p.url === currentTabPDF.url); const addResult = await chrome.runtime.sendMessage({
if (!exists) { action: 'addPDF',
console.log('[Popup] Adding current tab PDF to queue:', currentTabPDF.filename); pdf: currentTabPDF
const entry: PDFQueueEntry = { });
...currentTabPDF, if (addResult?.entry) {
status: 'pending' console.log('[Popup] Added current tab PDF to persistent queue:', currentTabPDF.filename);
};
pdfQueue.unshift(entry);
} }
} }
@@ -177,16 +175,19 @@ async function loadPDFQueue() {
console.log('[Popup] Queue empty, checking recent downloads...'); console.log('[Popup] Queue empty, checking recent downloads...');
const recentPDFs = await checkRecentDownloads(); const recentPDFs = await checkRecentDownloads();
for (const pdf of recentPDFs) { for (const pdf of recentPDFs) {
const exists = pdfQueue.some(p => p.url === pdf.url); // Add each PDF via background service (will check for duplicates/dismissed)
if (!exists) { await chrome.runtime.sendMessage({
pdfQueue.push({ action: 'addPDF',
...pdf, pdf
status: 'pending' });
});
}
} }
} }
// Reload queue after potential additions
response = await chrome.runtime.sendMessage({ action: 'getAllPDFs' });
pdfQueue = response?.entries || [];
console.log('[Popup] Final queue count:', pdfQueue.length);
// Render the list // Render the list
renderPDFList(); renderPDFList();
} }
@@ -757,7 +758,17 @@ async function handleRefreshStatus(id: string) {
* Handle dismiss PDF * Handle dismiss PDF
*/ */
async function handleDismissPDF(id: string) { async function handleDismissPDF(id: string) {
await chrome.runtime.sendMessage({ action: 'removePDF', id }); const pdf = pdfQueue.find(p => p.id === id);
if (!pdf) return;
// For pending/failed/in_basket items, mark as dismissed to prevent re-showing
// For completed items (sent/canceled), actually remove from storage
if (pdf.status === 'sent' || pdf.status === 'canceled') {
await chrome.runtime.sendMessage({ action: 'removePDF', id });
} else {
await chrome.runtime.sendMessage({ action: 'dismissPDF', id });
}
pdfQueue = pdfQueue.filter(p => p.id !== id); pdfQueue = pdfQueue.filter(p => p.id !== id);
renderPDFList(); renderPDFList();
} }

View File

@@ -11,6 +11,7 @@ const STORAGE_KEY = 'pdfQueue';
const MAX_ENTRIES = 50; const MAX_ENTRIES = 50;
const SENT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days const SENT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
const FAILED_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours const FAILED_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
const DISMISSED_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
export type PDFStatus = export type PDFStatus =
| 'pending' // Not yet uploaded | 'pending' // Not yet uploaded
@@ -20,7 +21,8 @@ export type PDFStatus =
| 'ordering' // Order in progress | 'ordering' // Order in progress
| 'in_production' // PRODUCTION_QUEUE or PRINTING | 'in_production' // PRODUCTION_QUEUE or PRINTING
| 'sent' // SENT - terminal | 'sent' // SENT - terminal
| 'canceled'; // CANCELED - terminal | 'canceled' // CANCELED - terminal
| 'dismissed'; // User dismissed, don't show again
export interface PDFQueueEntry extends DetectedPDF { export interface PDFQueueEntry extends DetectedPDF {
status: PDFStatus; status: PDFStatus;
@@ -70,10 +72,10 @@ export async function addPDF(pdf: DetectedPDF): Promise<PDFQueueEntry | null> {
// Check for duplicate by URL // Check for duplicate by URL
const existing = state.entries.find(e => e.url === pdf.url); const existing = state.entries.find(e => e.url === pdf.url);
if (existing) { if (existing) {
// Skip if already uploaded (in basket, production, or completed) // Skip if already processed (uploaded, dismissed, etc.)
const uploadedStatuses: PDFStatus[] = ['in_basket', 'ordering', 'in_production', 'sent', 'canceled']; const processedStatuses: PDFStatus[] = ['in_basket', 'ordering', 'in_production', 'sent', 'canceled', 'dismissed'];
if (uploadedStatuses.includes(existing.status)) { if (processedStatuses.includes(existing.status)) {
console.log('[PDF Queue] PDF already uploaded, skipping:', pdf.filename); console.log('[PDF Queue] PDF already processed, skipping:', pdf.filename, existing.status);
return null; return null;
} }
console.log('[PDF Queue] PDF already in queue:', pdf.filename); console.log('[PDF Queue] PDF already in queue:', pdf.filename);
@@ -160,7 +162,24 @@ export async function updatePDFStatus(
} }
/** /**
* Remove a PDF from the queue * Dismiss a PDF (mark as dismissed so it won't reappear)
*/
export async function dismissPDF(id: string): Promise<void> {
const state = await loadQueue();
const entry = state.entries.find(e => e.id === id);
if (!entry) {
console.warn('[PDF Queue] PDF not found for dismissal:', id);
return;
}
entry.status = 'dismissed';
await saveQueue(state);
console.log('[PDF Queue] Dismissed PDF:', id);
}
/**
* Remove a PDF from the queue (complete removal, used for cleanup)
*/ */
export async function removePDF(id: string): Promise<void> { export async function removePDF(id: string): Promise<void> {
const state = await loadQueue(); const state = await loadQueue();
@@ -177,11 +196,12 @@ export async function removePDF(id: string): Promise<void> {
} }
/** /**
* Get all PDFs for display in popup (all non-terminal statuses + recent terminal) * Get all PDFs for display in popup (excludes dismissed)
*/ */
export async function getAllPDFs(): Promise<PDFQueueEntry[]> { export async function getAllPDFs(): Promise<PDFQueueEntry[]> {
const state = await loadQueue(); const state = await loadQueue();
return state.entries; // Filter out dismissed entries - they're kept for duplicate detection but not displayed
return state.entries.filter(e => e.status !== 'dismissed');
} }
/** /**
@@ -254,6 +274,14 @@ export async function cleanupOldEntries(): Promise<void> {
} }
} }
// Remove dismissed entries older than 7 days
if (entry.status === 'dismissed') {
const age = now - entry.timestamp;
if (age > DISMISSED_MAX_AGE_MS) {
return false;
}
}
return true; return true;
}); });