From 6c8e70226087a4651090617f66042c713b6312f3 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sun, 3 May 2026 21:18:22 -0400 Subject: [PATCH] feat(ext/settings): retention section (trash + field history) --- extension/src/popup/components/settings.ts | 100 ++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/extension/src/popup/components/settings.ts b/extension/src/popup/components/settings.ts index c3c74c5..e975623 100644 --- a/extension/src/popup/components/settings.ts +++ b/extension/src/popup/components/settings.ts @@ -1,5 +1,5 @@ import { sendMessage, escapeHtml } from '../../shared/state'; -import type { VaultSettings, DeviceSettings } from '../../shared/types'; +import type { VaultSettings, DeviceSettings, TrashRetention, HistoryRetention } from '../../shared/types'; import type { ColorScheme } from '../../shared/color-scheme'; import { loadColorScheme, saveColorScheme, resetColorScheme, @@ -285,7 +285,103 @@ async function renderGeneratorSection(content: HTMLElement): Promise { } async function renderRetentionSection(content: HTMLElement): Promise { - content.innerHTML = '

Retention — coming soon

'; + content.innerHTML = '

Loading…

'; + const resp = await sendMessage({ type: 'get_vault_settings' }); + if (!resp.ok) { + content.innerHTML = `

Failed to load: ${escapeHtml(resp.error ?? 'unknown')}

`; + return; + } + const settings = (resp.data as { settings: VaultSettings }).settings; + pendingVaultSettings = { ...settings }; + + content.innerHTML = ` +

Trash retention

+
+
+
Keep deleted items for
+
Items in trash older than this are permanently deleted on the next sync.
+
+
+ +
+
+ +

Field history retention

+
+
+
Keep password history for
+
History entries older than this are pruned on save.
+
+
+ +
+
+
+ +
+ `; + + // Set current select values + (document.getElementById('trash-retention') as HTMLSelectElement).value = + trashRetentionToValue(settings.trash_retention); + (document.getElementById('history-retention') as HTMLSelectElement).value = + historyRetentionToValue(settings.field_history_retention); + + document.getElementById('trash-retention')?.addEventListener('change', (e) => { + if (pendingVaultSettings) { + pendingVaultSettings.trash_retention = valueToTrashRetention((e.target as HTMLSelectElement).value); + } + }); + + document.getElementById('history-retention')?.addEventListener('change', (e) => { + if (pendingVaultSettings) { + pendingVaultSettings.field_history_retention = valueToHistoryRetention((e.target as HTMLSelectElement).value); + } + }); + + document.getElementById('save-retention')?.addEventListener('click', async () => { + if (!pendingVaultSettings) return; + const r = await sendMessage({ type: 'update_vault_settings', settings: pendingVaultSettings }); + if (!r.ok) alert(`Save failed: ${r.error}`); + }); +} + +function trashRetentionToValue(r: TrashRetention): string { + if (r.kind === 'forever') return 'forever'; + return `days:${r.value}`; +} + +function valueToTrashRetention(v: string): TrashRetention { + if (v === 'forever') return { kind: 'forever' }; + const m = /^days:(\d+)$/.exec(v); + if (m) return { kind: 'days', value: Number(m[1]) }; + return { kind: 'forever' }; +} + +function historyRetentionToValue(r: HistoryRetention): string { + if (r.kind === 'forever') return 'forever'; + if (r.kind === 'last_n') return `last_n:${r.value}`; + return `days:${r.value}`; +} + +function valueToHistoryRetention(v: string): HistoryRetention { + if (v === 'forever') return { kind: 'forever' }; + const mLast = /^last_n:(\d+)$/.exec(v); + if (mLast) return { kind: 'last_n', value: Number(mLast[1]) }; + const mDays = /^days:(\d+)$/.exec(v); + if (mDays) return { kind: 'days', value: Number(mDays[1]) }; + return { kind: 'forever' }; } function renderBackupSection(content: HTMLElement): void {