feat(entries): encrypt template at rest, guided tour, import fixes, cleanup

Batched session work sharing app.js / index.html / Entries.pas, so it can't
split cleanly without interactive hunk staging.

- feat: encrypt `template` metadata at rest (template_enc/iv, added to
  ENCRYPTED_META_FIELDS). withEncryptedMeta skips an absent template key so
  partial re-ships (add-tag, move-to-folder) don't wipe it via LHasTemplate.
  Cleartext column kept as migration fallback. +3 unit tests.
- feat: first-run guided tour ("How it works") — spotlight + bubble, no GIFs,
  re-launchable from Settings, seen-flag in DPAPI prefs.
- fix(import): preserve original created_at on restore (was stamped to import
  time); restore entry icons on overwrite (PUT ignores icon_b64).
- fix(settings): correct clipboard-privacy copy (already excluded from Win+V);
  PIN text 4-6 -> 4-12; reorder Set-PIN above unlock-method; move tray/startup
  toggles to General; dedicated backup-password button + warning status; tab icons.
- chore: remove dead legacy monolith (app-legacy.js, index-legacy.html,
  style-legacy.css) + unused passkeyBtn stub.
- docs: full-source review (CODE_AUDIT 6b), template + favorite/pinned notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-07-11 18:32:37 +01:00
parent 92ed153bc0
commit a42e4b205d
15 changed files with 381 additions and 2173 deletions
+10
View File
@@ -414,6 +414,8 @@ function parseEntriesFromJSON(text) {
custom_fields: cf,
attachments: atts,
icon_b64: String(e.icon_b64 || '').trim(),
created_at: String(e.created_at || '').trim(),
updated_at: String(e.updated_at || '').trim(),
});
}
return { entries, skipped, columns: null, folders,
@@ -472,6 +474,10 @@ async function encryptImportEntry(plain) {
custom_fields_iv: cfIv,
icon_b64: plain.icon_b64 || '',
template: plain.template || '',
// Preserve original timestamps on restore — bulk-import falls back
// to now only when these are absent (foreign CSV imports).
created_at: plain.created_at || '',
updated_at: plain.updated_at || '',
});
}
@@ -693,6 +699,10 @@ async function doImport() {
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify(enc),
});
// PUT ignores icon_b64 (dedicated endpoint owns it), so
// restore the file's icon separately — else overwriting
// an entry whose icon was cleared never brings it back.
if (src.icon_b64) await saveEntryIcon(local.id, src.icon_b64);
overwritten++;
} catch (_) { /* skip the single row on failure */ }
}