Files
inkling/src/main/services/MediaGc.ts
altair823 3c780a7464 fix(trash): close active-query invariant leaks (review T5 important #1+#2)
T5 reviewer identified 2 reads outside NoteRepository that were missing the
'WHERE deleted_at IS NULL' filter, breaking the silent invariant beyond the
3 originally-listed methods.

- ContinuityService.get() now excludes trashed notes from streak / weekCount
  / lastNoteAt / recovery-toast math. A trashed note no longer counts toward
  weekly streak (regression: streak felt fake after trash).
- NoteRepository.getPendingCount() now excludes trashed-but-still-pending
  notes. trash() removes the pending_jobs row but leaves notes.ai_status='pending';
  the count would have drifted upward as users trashed pending notes.
- MediaGc.run() gets an inline comment documenting why it intentionally does
  NOT filter — trashed notes still own their media until permanentDelete /
  emptyTrash. Removing here would defeat restore.

Also: migrations.due_date.test.ts had 2 brittle assertions
(latestVersion()===2, user_version===2) that broke with v3. Migration-system
version assertions belong in migrations.test.ts (already covered there);
m002-specific test keeps the due_date column assertion which is version-stable.

Tests: 245 → 265 (+20). typecheck 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:58:18 +09:00

21 lines
808 B
TypeScript

import type Database from 'better-sqlite3';
import type { MediaStore } from './MediaStore.js';
export class MediaGc {
constructor(private db: Database.Database, private store: MediaStore) {}
async run(): Promise<{ removed: number }> {
const dirs = await this.store.listNoteDirs();
// Intentionally does NOT filter `deleted_at IS NULL` — trashed notes still own
// their media until permanentDelete/emptyTrash. Removing dirs of soft-deleted
// notes here would defeat restore.
const rows = this.db.prepare('SELECT id FROM notes').all() as Array<{ id: string }>;
const known = new Set(rows.map((r) => r.id));
let removed = 0;
for (const d of dirs) {
if (!known.has(d)) { await this.store.deleteNoteDirectory(d); removed += 1; }
}
return { removed };
}
}