Skip to content

feat(library): archiviare un'opera senza perderla - #449

Merged
nikazzio merged 2 commits into
mainfrom
feat/library-archive-source
Aug 31, 2026
Merged

feat(library): archiviare un'opera senza perderla#449
nikazzio merged 2 commits into
mainfrom
feat/library-archive-source

Conversation

@nikazzio

@nikazzio nikazzio commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Cosa cambia

Un'opera della Biblioteca può ora essere archiviata: sparisce dall'elenco senza uscire dal catalogo, e si riporta indietro quando serve.

  • nella barra di ricerca c'è un comando (icona cassetta) che fa rientrare le archiviate nei risultati; di default restano fuori;
  • su ogni riga il comando archivia/riporta in catalogo, con lo stato visibile nel comando stesso e la riga in tono più tenue quando è archiviata;
  • archiviare non tocca il disco. Subito dopo, se quell'opera occupa spazio, arriva una domanda separata: «liberare anche N MB?». Si può dire di no e farlo più tardi;
  • la rimozione resta com'era: immediata e definitiva. Nessun cestino intermedio.

Note tecniche

  • schema consolidato nella baseline (pre-1.0, nessun dato reale distribuito): sources.status passa a ('active','archived') con archived_at, e perde lo stato trashed che nessuna parte del programma usava. Serve ricreare il database locale (glossa.db + WAL/SHM) per provare questa PR;
  • il catalogo continua a fare una sola lettura: le archiviate arrivano insieme alle altre e vengono nascoste dai filtri di vista, come già si fa per ricerca e filtri;
  • le tendine dei filtri derivano dalle opere realmente visibili, per non offrire scelte che non selezionano niente;
  • 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

  1. Archiviare senza file: opera solo online → archivia → sparisce dall'elenco, nessuna domanda sullo spazio.
  2. Archiviare con pagine scaricate: archivia → compare la domanda «liberare anche …?». Rispondendo no, le pagine restano (riaccendendo le archiviate, la riga dice ancora quante pagine ci sono). Rispondendo , lo spazio viene liberato e la riga torna «solo online».
  3. Rivedere le archiviate: comando cassetta nella barra → le archiviate rientrano in elenco, in tono più tenue; ricerca, tipo, lingua, biblioteca e disponibilità continuano a filtrare anche loro.
  4. Riportare in catalogo: sulla riga archiviata, stesso comando → torna attiva e resta visibile anche spegnendo il filtro.
  5. Azzera filtri (gomma): rimette anche le archiviate fuori dai risultati.
  6. Rimozione: invariata — conferma, poi l'opera e i suoi file spariscono per sempre.
  7. Documentazione: guida pubblica IT/EN aggiornata con archiviazione e barra dei filtri.

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

  • La richiesta che archivia cambiava forma a seconda del caso (la data finiva dentro al testo della richiesta): ora la richiesta è sempre la stessa e la data 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» una riga che la vista può aver già nascosto.
  • Il test del rifiuto mancava: si chiamava «lo libera solo se si accetta» ma copriva solo il sì. Aggiunto il caso del no, e le chiamate registrate ora si azzerano fra un caso e l'altro — senza, una chiamata identica del caso precedente avrebbe fatto passare l'asserzione da sola.

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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/archivedAt alle 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 di busy per 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.

Comment on lines 357 to 361
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);
Comment on lines +614 to +615
onClick={() => void (archived ? onSetArchived(false) : archive())}
title={archived ? t('areas.library.restore') : t('areas.library.archive')}
Comment on lines +300 to +319
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());
});
Comment on lines +224 to +234
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.
@nikazzio
nikazzio merged commit 99212fa into main Aug 31, 2026
11 checks passed
@nikazzio
nikazzio deleted the feat/library-archive-source branch August 31, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants