Knowledge base

Library and trash

Why Delete asks nothing and Delete forever asks twice, what 90 days actually governs, and what a backup does and does not contain.

Em dashes in quoted shipped strings are shown as plain hyphens; the originals use em dashes.

The 90 days, and why you can trust the number

Deleted items wait 90 days before they are purged for good. The number is RETENTION.deletedGraceDays in packages/core/src/constants.ts:182. The web app imports it everywhere it appears (apps/web/src/lib/library/model.ts, purge.ts). The desktop cannot import it (no bundler), so it carries its own GRACE_DAYS = 90 (apps/desktop/src/board.js, search the identifier - the line moves and this cite has been stale twice), and scripts/check-promises.mjs fails the build if the two copies ever differ, and if the web ever grows a second local copy (joined-to-code: scripts/check-promises.mjs).

Delete versus Delete forever

Delete asks no confirmation BECAUSE it is reversible for the whole grace period. Delete forever asks BECAUSE it is not: on the web, an inline details block reads "This removes the item and its stored file permanently. It cannot be undone." (apps/web/src/app/dashboard/asset-actions.tsx:99); on the desktop it is a counting confirm (joined-to-code: cleanup and trash harness checks).

Purging a live item on the web is refused server-side: "Delete it first - this only empties the trash." (apps/web/src/lib/library/actions.ts:117, em dash shown as hyphen).

On the desktop, files are touched only at purge: delete never removes a recording file; purge removes card and file together or keeps both (joined-to-code: trash harness checks). The purge sweep runs once per launch, and a locked file keeps its card for the next launch rather than orphaning the file (joined-to-code: harness). Trash is sorted by deletion time, and the item count in the header counts live cards only (joined-to-code: harness).

Desktop delete shows the toast "Moved to Trash - restorable for 90 days" (apps/desktop/src/board.js, search the toast text - the line moves). There is no undo button in that toast today; that gap is already filed.

Deleting a capture kills its share link immediately; restoring revives it. Pinned live by apps/web/e2e/library.spec.ts: the link answers 200, then 404 while trashed, then 200 after restore (joined-to-code). The web trashed-asset page says it in one banner: "This is in the Trash. {days left} - after 90 days it and its stored file are removed for good. Its share link does not work while it is here." (apps/web/src/app/dashboard/a/[id]/page.tsx:109-113, em dash shown as hyphen). On the desktop, the card in the trash view simply loses its Copy link button by the button swap; the web viewer is where the link actually dies. Cross-reference sharing.md.

Empty states are three different sentences on purpose

  • Trash empty: the trash view's own empty message.
  • Filter matched nothing: "No items match this filter." Your work is hidden, not gone (apps/web/src/app/dashboard/page.tsx:114).
  • Genuinely empty: "No captures yet.", a Take your first capture button for editors, and "Captures from the desktop app and the browser extension land here too." (apps/web/src/app/dashboard/page.tsx). Until 2026-08-26 this sentence pointed only at the other two apps; the web app takes captures itself now.

A failed read is its own card, not an empty state: "Nothing has been deleted: this is a read failure, not an empty library." (apps/web/src/app/dashboard/page.tsx:104).

The web list caps at 200 rows and admits it: "Showing the first {200}. Narrow the filter or the search to see the rest." (apps/web/src/app/dashboard/page.tsx:131). The counts line shows rows listed, live items and trash items (joined-to-code: e2e/library.spec.ts header counts).

Triage

Triage is the human's verdict, separate from the pipeline status: a click-cycle open to fail to pass to open, the same cycle on web and desktop (TRIAGE_STATUSES in packages/core/src/constants.ts:244, nextTriage in apps/web/src/lib/library/model.ts:162, joined-to-code: model tests). It is exposed as a badge-shaped button whose hover title says "Mark as {next}" (apps/web/src/app/dashboard/asset-actions.tsx:36).

In the trash the badge is display-only: triage still shows, but clicking it does nothing until the item is restored, matching every other write the trash hides (apps/web/src/app/dashboard/asset-actions.tsx, TriageBadge). The stoic pass fixed the earlier behaviour where it kept cycling there.

Backup and restore (desktop)

Backup writes one JSON file, named snapbuddy-backup-YYYY-MM-DD (apps/desktop/src/core.js:744), containing every card: notes, statuses, thumbnails as data URLs, annotations with their time windows, file name and format choices, share links, and trash tombstones with their clocks. Recording video files are NOT inside it. A card restored on another machine reports honestly: "That recording is not on this machine any more: {path}" (apps/desktop/src/board.js:569).

Restore ADDS cards, merging by id, and sanitizes everything: unknown fields dropped, share links rejected unless http(s), annotations capped at 200, and trimmed/recovered/deleted_at whitelisted explicitly (joined-to-code: anno-survive-restore, tombstone, name/format and share-link harness checks).

Sentences a support agent can paste

"Deleted items wait in the Trash for 90 days. While something is in the Trash its share link does not work; restoring it brings the link back. After 90 days the item and its stored file are removed for good."

"Deleting a card never deletes the recording file; only Delete forever, or the 90-day purge, removes files."

"A backup carries your cards and notes, not the video files; keep the recordings folder itself if you need the footage."