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>
Frontend unit tests
Regression net for the highest-risk pure/near-pure logic in js/app.js:
crypto round-trip, KDF/verifier derivation, CSV import parsing, and the
sync-merge / tombstone-resurrection arbitration. Addresses CODE_AUDIT.md
§3.2 (aucun test automatisé).
Running
npm test
# or directly:
node --test "js/tests/**/*.test.js"
Zero dependencies — uses the Node built-in test runner (node:test) and
webcrypto. Requires Node ≥ 18 (developed on v24). Runs in ~1.7 s.
How it works — harness.js
The frontend is a large browser script with no module exports and one
top-level side effect (a DOMContentLoaded listener). It's being split into
ordered classic-script files (§3.1); the harness concatenates the app
parts in load order (APP_PARTS = app.crypto.js + app.js) into one
source — node:vm doesn't share top-level const/let across separate
runInContext calls the way the browser shares them across <script> tags.
argon2.js is a self-contained IIFE and loads separately first. The
concatenated source runs in a node:vm context with browser globals stubbed
(crypto, localStorage, document, location, …) so init() never
fires, then an export epilogue surfaces the internals on globalThis.__test.
Two gotchas the harness works around, both documented inline:
const/letdon't attach to the vm global. Top-levelfunction/vardeclarations become properties of the context global, butconst state,const HASH_ALGO_V2, etc. do not — hence the explicit export epilogue.- Cross-realm prototypes. Values returned from the sandbox carry the
sandbox realm's prototypes, so
assert.deepStrictEqualtrips on the prototype check. Structural comparisons normalize through JSON first (seeeqDeepincsv.test.js).
The merge tests stub only the api() seam (a function declaration →
overridable global property) with an in-memory fake server; loadEntries,
loadFolders, and encryptImportEntry all run for real against it — so the
tests exercise the actual pull→merge path, not a re-implementation.
Suites
| File | Covers |
|---|---|
crypto.test.js |
deriveKeyAndVerifier (AES key == raw PBKDF2, cross-checked vs Node's pbkdf2Sync), legacy vs -v2 verifier decoupling, encryptPwd/decryptPwd round-trip, IV uniqueness, AEAD tamper/wrong-key → [ERROR] |
csv.test.js |
parseCSV tokenizer (quotes, escaped "", CRLF, trailing field), findColumn header heuristics, parseEntriesFromCSV for Bitwarden/KeePass shapes, note-vs-login classification |
merge.test.js |
applyRemoteSnapshot: add/update/skip (last-write-wins), tombstone delete, resurrection arbitration (both NaN branches), local-tombstone veto, additive folder merge |
Notes on encoded behavior
merge.test.js locks in one deliberately-asymmetric behavior: an unparseable
local updated_at favours KEEP (resurrection wins), but an unparseable
remote deleted_at still applies the delete. In production deleted_at
is always server-formatted (parseable), so this only matters for a corrupted
remote snapshot. If that arbitration is ever changed, the two
unparseable … tests are where to update the expectation.