Auto-Expiry Logic
Relevant source files
The following files were used as context for generating this wiki page:
Auto-Expiry Logic is a core system in Augenblick designed to manage the lifecycle of temporary notes. It automatically transitions inactive notes to a trash state and eventually deletes them permanently based on user-defined retention settings. This ensures the workspace remains uncluttered by removing information that hasn't been interacted with for a specific duration.
The system operates through a combination of background processes in the Rust backend, state management in the React frontend, and visual indicators that inform the user of a note's remaining lifespan. Notes that are explicitly "Pinned" are exempt from this expiry logic, allowing users to keep important information indefinitely.
Sources: README.md:14-15, plans/migration.inventory.md:167-170
Architecture and Data Flow
The auto-expiry system is distributed across the backend and frontend to ensure both data integrity and a responsive user interface.
Backend Sweeper
The Rust backend maintains an "Auto sweeper" thread that runs every 60 seconds. This thread performs two primary tasks:
- Auto-trash: It identifies unpinned, active notes where the
last_interactiontimestamp is older than the configuredexpiry_days. - Auto-delete: It identifies trashed notes where the
trashed_attimestamp is older than the configuredtrash_retention_days.
Sources: plans/migration.inventory.md:167-176
Frontend Synchronization
The React frontend synchronizes with this logic through several mechanisms:
- Initialization: On app launch, the
notesStoretriggers an immediate expiry run viaexpiry_run_now. - Calculated Timers: The
App.tsxcomponent calculates the time until the next note is set to expire and schedules asetTimeoutto trigger a refresh of the notes list. - Heartbeat: Every 30 seconds, the app calls
note_set_activefor the currently selected note to prevent it from expiring while being viewed.
Sources: src/App.tsx:210-225, src/App.tsx:327-329, src/stores/notesStore.ts:54-56
Expiry Data Flow Diagram
The following diagram illustrates how interaction timestamps move through the system to trigger state changes.
Lifecycle States and Triggers
A note's lifecycle is determined by specific timestamps and user actions.
Retention Settings
The system uses three primary configuration options to determine timing:
| Option | Type | Default | Description |
|---|---|---|---|
expiry_days | Integer | 7 days | Inactivity threshold to move a note to Trash. |
trash_retention_days | Integer | 30 days | Duration a note stays in Trash before permanent deletion. |
is_pinned | Boolean | false | If true, the note is exempt from the expiry_days check. |
Sources: plans/migration.inventory.md:167-172, src/lib/tauri/shim.ts:13-17
Interaction and Expiry Calculations
Expiry is calculated based on last_interaction (the last time a note was opened or edited) or trashed_at (the time the note entered the trash).
- Interaction Update: The
last_interactiontimestamp is updated duringnote_create,note_write_draft,note_save,note_set_active, and when a note isunpinned. - Expiry Time:
expiryAt = lastInteractionMs + (expiryMinutes * 60,000). - Deletion Time:
deleteAt = trashedAt + (trashRetentionDays * 86,400,000).
Sources: src/lib/utils/expiry.ts:15-17, plans/migration.inventory.md:104-106, src-tauri/src/notes/write.rs:101-103
Visual Indicators (Expiry Ring)
The frontend provides a visual "Expiry Ring" in the sidebar for unpinned active notes. This ring represents the remaining time before a note is moved to the trash.
Progress and Thresholds
The ring's progress is calculated as clamp01(1 - elapsed / totalMs). The color of the ring changes based on the percentage of time remaining:
| Status | Threshold | Stroke Color Variable |
|---|---|---|
| Fresh | ≥ 50% | var(--ring-green) |
| Aging | 25% - 49% | var(--ring-yellow) |
| Warning | 10% - 24% | var(--ring-orange) |
| Danger | < 10% | var(--ring-red) |
Sources: src/lib/utils/expiry.ts:7-29, src/test/features/ExpiryRing.test.tsx:32-75, plans/migration.inventory.md:183-186
Tooltip Logic
The tooltip for the expiry ring and trash items uses relative time formatting to provide human-readable estimates.
- Active Note: Displays
Trashed [Relative Time From Now](e.g., "Trashed in 3d"). - Trashed Note: Displays
Deletes [Relative Time From Now](e.g., "Deletes in 14d").
Sources: plans/migration.inventory.md:52-65, src/test/features/ExpiryRing.test.tsx:35-37
Implementation Details
Rust Sweeper Logic (Web Shim Equivalent)
In environments where the full Rust backend is not available (such as the web shim used for testing), the expiry logic is replicated in TypeScript to maintain parity.
case "expiry_run_now": {
const expiryMinutes = db.settings?.expiryMinutes ?? DEFAULT_SETTINGS.expiryMinutes;
const cutoff = now() - Math.max(1, expiryMinutes) * 60_000;
for (const entry of Object.values(db.notes)) {
if (entry.meta.isTrashed || entry.meta.isPinned) continue;
if (entry.meta.lastInteraction > cutoff) continue;
const t = now();
entry.meta = { ...entry.meta, isTrashed: true, isPinned: false, trashedAt: t };
}
saveDb(db);
return;
}
Sources: src/lib/tauri/shim.ts:241-253
Note Heartbeat
To prevent a note from expiring while a user is actively reading or editing it, the App.tsx component implements a heartbeat. Every 30 seconds, it invokes the note_set_active command for the selectedId, which updates the last_interaction timestamp in the database to the current time.
Sources: src/App.tsx:327-329, src-tauri/src/notes/write.rs:101-103
Summary
The Auto-Expiry Logic facilitates a "temporary-by-default" workflow. By leveraging a background sweeper thread in Rust and real-time visual feedback in React, the system automates the transition of notes from Active to Trash, and eventually to permanent deletion. This lifecycle is governed by user-configurable duration settings and can be bypassed for specific notes through the pinning mechanism.