feat(library): archiviare un'opera senza perderla - #449
Conversation
Un'opera della Biblioteca ora vive in due stati: attiva o archiviata.
Archiviarla la toglie dai risultati senza farla uscire dal catalogo; il
comando nella barra di ricerca la riporta in vista e quello sulla riga la
rimette in circolo. La rimozione resta quello che era: immediata e
definitiva, senza cestino intermedio da mantenere.
Archiviare riguarda solo il catalogo, mai il disco. Siccome è il momento in
cui ci si accorge dello spazio occupato, subito dopo si propone di liberarlo
come domanda separata: niente viene cancellato senza una risposta esplicita.
Note tecniche:
- schema consolidato nella baseline (pre-1.0, nessun dato reale): sources
passa a status IN ('active','archived') con archived_at, e perde lo stato
di cestino che nessuna parte del programma usava;
- il catalogo legge tutte le opere in una query sola e nasconde le archiviate
nei filtri di vista, coerente con la scelta fatta per ricerca e filtri;
- le tendine dei filtri offrono i valori delle opere davvero visibili, per non
proporre scelte che non selezionano niente.
There was a problem hiding this comment.
🟡 Changes recommended
Ci sono problemi concreti nel flusso UI/test (archiviazione + liberazione spazio) che possono generare warning runtime e asserzioni di test potenzialmente false-positive.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Questa PR introduce lo stato di archiviazione per le opere della Biblioteca: le opere archiviate restano nel catalogo ma vengono nascoste dalla vista standard, con un comando per mostrarle e un’azione per archiviarle/ripristinarle, mantenendo separata la liberazione dello spazio su disco.
Changes:
- Aggiunge
status/archivedAtalle fonti di Biblioteca e aggiorna query/catalogo per includere anche le archiviate. - Estende filtri e UI per nascondere/mostrare archiviate, con comandi di archiviazione/ripristino e prompt separato per liberare spazio.
- Aggiorna test, migrazione DB e documentazione (IT/EN + architettura).
File summaries
| File | Description |
|---|---|
| src/utils/libraryCatalogFilters.ts | Aggiunge includeArchived ai filtri e nasconde le fonti archiviate quando il flag è spento. |
| src/utils/libraryCatalogFilters.test.ts | Copre il comportamento di esclusione/inclusione delle archiviate e il “filtro attivo”. |
| src/types.ts | Introduce SourceStatus e i campi status/archivedAt su LibrarySource. |
| src/stores/sourceLibraryStore.ts | Aggiunge azione di store setArchived per archiviare/ripristinare e ricaricare i dati. |
| src/stores/sourceLibraryStore.test.ts | Aggiorna fixture/mock per includere status/archivedAt. |
| src/services/libraryService.ts | Legge/scrive status e archived_at; rimuove il filtro DB che escludeva le archiviate dal catalogo. |
| src/i18n/it.json | Aggiunge stringhe UI per mostra archiviate, archivia/ripristina e prompt liberazione spazio. |
| src/i18n/en.json | Aggiunge stringhe UI corrispondenti in inglese. |
| src/components/workspace/LibraryFilterBar.tsx | Aggiunge toggle “mostra archiviate” e reset filtri via EMPTY_LIBRARY_FILTERS. |
| src/components/workspace/LibraryCatalogArea.tsx | Introduce comandi di archiviazione/ripristino per riga e prompt “liberare anche…”. |
| src/components/workspace/LibraryCatalogArea.test.tsx | Aggiunge casi di test per archiviazione e visibilità delle archiviate. |
| src/components/dashboard/SourceDiscoveryPanel.test.tsx | Estende mock del service per includere setSourceArchived. |
| src-tauri/src/db.rs | Aggiorna test schema per verificare la presenza di sources.archived_at. |
| src-tauri/migrations/0001_baseline_2_0.sql | Migra sources.status a active/archived, introduce archived_at e indice su status. |
| docs/guides/library-discovery.md | Documenta archiviazione e comportamento della barra filtri (IT). |
| docs/en/guides/library-discovery.md | Documenta archiviazione e comportamento della barra filtri (EN). |
| docs-dev/ARCHITECTURE.md | Registra la scelta architetturale: 2 stati e filtro lato vista, non query separata. |
Review details
Suppressed comments (1)
src/components/workspace/LibraryCatalogArea.tsx:408
- Dopo la conferma “liberare anche…”, questa chiamata può partire quando la riga è già stata rimossa dalla lista (archiviata + filtro che la nasconde). Con la modifica a
runFreeSpace(trackBusy), qui è meglio disattivare l’aggiornamento dibusyper evitare setState su componente smontato.
if (alsoFree) await runFreeSpace();
- Files reviewed: 17/17 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const runFreeSpace = async () => { | ||
| if (!entry.versionId) return; | ||
| const confirmed = await confirm({ | ||
| title: t('areas.library.freeSpaceTitle', { size: humanSize(entry.localBytes) }), | ||
| message: t('areas.library.freeSpaceMessage'), | ||
| confirmLabel: t('areas.library.freeSpaceConfirm'), | ||
| danger: true, | ||
| }); | ||
| if (!confirmed) return; | ||
|
|
||
| setBusy(true); | ||
| try { | ||
| const freed = await freeVersionPages(await providerKey(), entry.versionId); |
| onClick={() => void (archived ? onSetArchived(false) : archive())} | ||
| title={archived ? t('areas.library.restore') : t('areas.library.archive')} |
| it('dopo aver archiviato propone di liberare lo spazio, e lo libera solo se si accetta', async () => { | ||
| const service = await import('../../services/libraryService'); | ||
| const { freeVersionPages } = await import('../../services/vaultService'); | ||
| useSourceLibraryStore.setState({ | ||
| catalog: [ | ||
| entry({ | ||
| localPages: 34, | ||
| localBytes: 48_234_496, | ||
| principalSize: '2000', | ||
| sizes: [{ sizeTag: '2000', pages: 34, bytes: 48_234_496, missing: 0 }], | ||
| }), | ||
| ], | ||
| }); | ||
|
|
||
| render(<LibraryCatalogArea />); | ||
| fireEvent.click(screen.getByRole('button', { name: 'areas.library.archive' })); | ||
|
|
||
| await waitFor(() => expect(service.setSourceArchived).toHaveBeenCalledWith('s1', true)); | ||
| await waitFor(() => expect(freeVersionPages).toHaveBeenCalled()); | ||
| }); |
| export async function setSourceArchived(sourceId: string, archived: boolean): Promise<void> { | ||
| await execute( | ||
| `UPDATE sources | ||
| SET status = $1, | ||
| archived_at = ${archived ? 'CURRENT_TIMESTAMP' : 'NULL'}, | ||
| updated_at = CURRENT_TIMESTAMP | ||
| WHERE id = $2`, | ||
| [archived ? 'archived' : 'active', sourceId], | ||
| ); | ||
| logger.info(archived ? 'library.source.archived' : 'library.source.restored', { sourceId }); | ||
| } |
…erenti, test più severi Tre osservazioni della review, verificate nel codice: - la richiesta che archivia cambiava forma a seconda del caso (una data scritta dentro il testo della query): ora è sempre la stessa, con la data che segue lo stato fra i valori passati. Aggiunto il test che lo verifica; - riportare in catalogo non spegneva i comandi della riga: due click di seguito erano due richieste. Ora si comporta come l'archiviazione; - liberare lo spazio dopo l'archiviazione non segna più «occupato» la riga: quando la vista nasconde le archiviate quella riga non esiste più, e scriverci sopra è scrivere su qualcosa che non c'è; - il test diceva «lo libera solo se si accetta» ma il rifiuto non era coperto, e le chiamate registrate non venivano azzerate fra un caso e l'altro: una chiamata identica del caso precedente avrebbe fatto passare l'asserzione da sola. Aggiunto il caso del rifiuto e l'azzeramento.
Cosa cambia
Un'opera della Biblioteca può ora essere archiviata: sparisce dall'elenco senza uscire dal catalogo, e si riporta indietro quando serve.
Note tecniche
sources.statuspassa a('active','archived')conarchived_at, e perde lo statotrashedche nessuna parte del programma usava. Serve ricreare il database locale (glossa.db+ WAL/SHM) per provare questa PR;freeSpaceè stato spezzato in «chiedi» ed «esegui», così la stessa cancellazione serve sia il comando diretto sia la proposta dopo l'archiviazione, senza duplicare la logica.Da testare
CI verde su lint, TypeScript, test frontend e backend.
Issue
Parte di #187 (voce «archiviazione» del lavoro residuo dell'epic). L'epic resta aperta: mancano ancora arricchimento controllato dei metadati, manutenzione e i collegamenti a trascrizioni e traduzioni.
Review risolta