8e0e7fd3301999229a839da79824d54750a946b7
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d9397881dc |
test: add frontend unit suite + fix mixed local/UTC timestamps
Two CODE_AUDIT items in one session. §3.2 — Frontend regression net (js/tests/, 35 tests, node:test, zero deps): - harness.js loads app.js (monofile, no exports) into a node:vm with browser globals stubbed, surfacing internals via an export epilogue. - crypto: deriveKeyAndVerifier (AES key == raw PBKDF2, cross-checked vs Node pbkdf2Sync), legacy-vs-v2 verifier decoupling, encrypt/decrypt round-trip, IV uniqueness, AEAD tamper/wrong-key. - csv: parseCSV tokenizer, findColumn heuristics, Bitwarden/KeePass mapping. - merge: applyRemoteSnapshot add/update/skip (LWW), tombstone delete, resurrection arbitration (both NaN branches), local-tombstone veto, additive folder merge. Only api() is stubbed; loadEntries/encryptImportEntry run for real. - Wired as a build gate in BuildAssets.ps1 (after node --check, bypass PM_SKIP_TESTS=1). §2.2 — Unify timestamps on UTC: - Entry created_at/updated_at were written via Delphi FormatDateTime(Now) = LOCAL, while deleted_at/tombstones use SQLite CURRENT_TIMESTAMP = UTC. The tombstone-resurrection arbitration compared the two zones, skewing by the machine's UTC offset even single-device. - Add NowUTC/NowUTCStr to PM.Database, swap in at every entry/attachment write site (Entries create/update/bulk, Attachments POST echo). - No JS change needed: arbitration now compares same-zone values. - Existing rows self-heal on next edit (no destructive migration). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ac909f4f09 |
feat: sync ETag concurrency + fix chunk-transfer hang + sync overlay + auto-VACUUM
Sync optimistic concurrency (ETag/If-Match) - webdav GET captures the response ETag; PUT sends it back as If-Match so the server rejects (412) our write when another device changed the file between our pull and push. A 412 re-runs the whole pull→merge→push (bounded to 3) so the other device's changes are folded in instead of clobbered. Servers without ETags → empty etag → no If-Match → falls back to last-write-wins (no regression). onWebdavResult gained a 4th etag arg. Chunked webdav PUT (big vaults no longer black-screen on sync) - The whole encrypted snapshot base64'd into a single cmd://webdav/put URL blew past WebView2's cap → black screen once the vault grew (20MB of attachments). PUT bodies now stream through the file/chunk transport and commit via a new webdav/put-commit (reads the accumulated buffer). Chunk-transfer hang fix (root cause of the stuck "Preparing…" sync) - All chunked transfers (saveFile/writeFile/webdav PUT) share one reqId-keyed resolver. A resolved chunk's stale 30s timeout would later delete the CURRENT chunk's resolver and fire the wrong res(), leaving that chunk's await pending forever. Extracted a single _streamChunks() helper whose ack CLEARS the pending timeout, so resolvers stay strictly one-at-a-time. Also fixed _webdavCall referencing the Bridge-local cmd() from module scope (latent ReferenceError). Sync busy overlay - syncStatus() now drives the global busy overlay too, so a running sync blocks stray clicks (e.g. the auto-backup "Choose…" picker) and reads like the manual backup. The account-mismatch confirm hideBusy()s first so it's visible above the overlay. Auto-VACUUM (reclaim space after deleting large attachments) - SQLite never shrinks the file on DELETE, so deleting big attachments left vault.db bloated (35MB for 11 tiny entries). DB.CompactIfBloated VACUUMs when >20% of pages are free AND >~2MB is reclaimable — called on startup and after each attachment delete. A healthy small vault pays nothing. (Verified: 35MB → 695KB after the deletes.) Rebuild: BuildAssets + F9 (UMainForm + PM.Database + PM.Handler.Attachments). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
b00da43ab0 |
feat: PIN unlock + table column picker + edit-position chooser + UX
- PIN unlock: device-local 4-12 digit shortcut, DPAPI-wrapped vault
key. Three modes (state.unlockMode): pw / pin / pw+pin. PIN
derives a wrap key via PBKDF2(pin, salt, 100k) and unwraps the
stored vault key (mirrors the Quick Unlock blob shape).
Anti-brute-force: 5 wrong attempts wipes the blob. Setup gated by
master-pw reauth so an unattended unlocked laptop can't be
backdoored. Master pw rotation clears the PIN blob (key drift).
loadServerSettings post-sync demotes pin/both -> pw when the local
blob is missing, so a wiped device re-syncs the correct mode up.
New unit PM.PinUnlock.pas + cmd://pin/{store,get,clear,status}.
- Table column picker: ⚙ in topbar (table view only), checkbox menu
for Site/Username/Folder/Updated. Site also drives showSiteOnCards
so the existing "Show site / URL" toggle in Settings stays in
sync. NAME column auto-widths (180px min, content max, +32px
right padding) so column hugs the next one without truncating.
- Editor position chooser (Appearance setting): Slide-over right /
left / Centered modal. Scoped to #slideover + #settingsPanel so
the click-outside / pointer-events logic doesn't accidentally
trap the modal-style empty viewport.
- Confirm before discarding unsaved edits: state.confirmOnUnsaved
setting (default ON), prompts on X / Esc / click-outside / switch-
to-other-entry. Also gates Lock vault / Sign out actions when the
editor is dirty; auto-lock and system-lock paths bypass to avoid
blocking on an unattended machine.
- Open-in-browser button added to the actions cell of the table
view (was card-only).
- Entry templates pass folder customization + template id through
duplicate / export / import / auto-backup roundtrips.
- Folder color + icon now persisted across export/import: payload.
folders carries name/color/icon; import creates missing folders
additively (existing local customisation kept).
- Bulk move-to-folder, batch add-tag, single add-tag now re-ship
the full entry payload so partial PUTs don't silently wipe
TOTP / custom_fields / kind / template.
- FireDAC: switched ftString -> ftMemo for icon_b64 / custom_fields
/ TOTP / template params and replaced .AsString with .Value so a
large (~200 KB) DeepSeek favicon no longer gets truncated at the
default ANSI 4000-char cap.
- Unicode filenames: attachment INSERT now uses ftWideString +
.AsWideString so non-ANSI filenames round-trip instead of being
mangled to "?".
- HandleSetEntryIcon cap raised 256 KB -> 512 KB chars to accept
base64 data URIs produced by max-raw favicon fetches.
- promptDialog + askReauth support inline `error` line + retry-
with-count loops on doExport reauth and auto-backup password
setup (5 attempts cap before bailing).
- Recently used moved from Tools to Vault section in the sidebar.
- Auth screen passkey button hidden (Delphi backend stubs WebAuthn).
- Sensitive cmd://favicon/refresh-style buttons in Settings now
stopPropagation so the document-level "close panel" handler
doesn't dismiss Settings mid-async during DOM reparenting.
- TEST_PLAN.md: +PIN unlock section.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
e23a78dda7 |
feat: entry templates + tag autocomplete + slideover push + robustness bundle
- Entry templates: new vault_entries.template column drives a typed
sub-kind ('credit-card', 'ssh-key', 'server', 'recovery-codes'). Card
+ table label off the template, badge reads "credit card" instead of
"note". Templates seed kind=note (no site/password required), use
custom_fields with optional dropdown options (brand, month/year,
protocol). Round-tripped across export/import/duplicate/master-pw
rotation, preserved by partial PUTs via a HasTemplate flag.
- Custom fields: support per-field `options[]` rendering as <select>
(card brand, expiry MM/YYYY, SSH/server protocol).
- Tags: existing-tag autocomplete dropdown under the chip input,
filtered against what's already selected.
- Search history: per-query X for individual delete + 1s debounced
commit (no Enter required).
- Slideover: clicking outside closes again (drag-selection respected
via mousedown origin tracker), Esc closes, X closes. App shell is
pushed left by 420px when the panel is open so the table / pagination
/ sort / search stay visible and interactive.
- Export/import: JSON now round-trips custom_fields, attachments
(decrypted to base64, re-encrypted under current key on restore),
icon_b64, and template. CSV warning lists what's not included.
- Auto-backup: same payload shape as user-driven export.
- Notes: import (JSON + CSV) accepts kind=note with empty site,
preserves title/template/custom_fields. CSV parser detects kind/
template columns.
- Bulk-import response returns `ids[]` parallel to input so the
client can map back to new entry IDs (drives attachment restore).
- Move-to-folder bugs fixed: moveEntryToFolder, batchMoveToFolder,
addTag, batchAddTag were all silently wiping TOTP / custom_fields
/ kind / template via partial PUT. Now re-ship full payload.
- Master-pw rotation: server mints a fresh session token + csrf so
the very next request after rotation no longer ESessionRejects.
Client adopts the new pair. Attachments are re-encrypted client-side
during rotation (GET old → decrypt with old key → encrypt with new
→ PUT). New endpoints: GET /attachments/all, PUT /attachments/:id.
- Duplicate: carries icon_b64 + template + attachments to the copy.
- HandleCreateEntry: accepts icon_b64.
- FireDAC param fix: all blob/icon/custom_fields params use ftMemo +
.Value assignment so SQLite TEXT no longer truncates to 4000 chars
(deepseek's 200+ KB favicon was being wiped on lock/unlock).
- HandleSetEntryIcon cap: 262144 → 524288 chars (base64 of a 256 KB
raw fetch overflows the old cap, fails silently in saveEntryIcon).
- Native save dialog: surfaces server errors instead of swallowing.
- Modals: reauth (export) + backup-password prompt support inline
error display, retry up to 5 attempts, then hard-stop.
- Keyboard cursor (j/k): bootstraps to current page, auto-paginates
when the cursor crosses a page boundary, Enter opens slideover.
- Slideover focuses Title on edit-open so j/k → Enter → type Just
Works.
- TOTP tool: Esc closes the modal.
- App version + launch mode (auto/manual): exposed via bridge,
surfaced in Settings → Account. Autostart launches suppress the
first-time tray balloon.
- Passkey button hidden (Delphi backend stubs WebAuthn at 501).
- TEST_PLAN.md captured for regression coverage.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
fa7ea191be |
feat: native save + auto-backup + folder customization + attachments + UX bundle
- File: native Save As dialog via Bridge.saveFile (replaces WebView2
browser download popup) for encrypted JSON + CSV exports.
- Auto-backup: silent periodic encrypted JSON to a chosen folder,
user-set interval + retention, separate DPAPI-stored password, runs
5s after unlock if due. New file/* bridge cmds (folder/pick,
file/write, file/listMatch, file/delete).
- Folders: per-folder color + icon (8-swatch palette, 8 icon presets),
drag-reorder via HTML5 DnD with insert-line indicators, edit pencil
on hover. New POST /folders/reorder + PUT /folders/{name}. Folder
chip on cards inherits custom icon + color.
- Recently used: vault_entries.accessed_at + POST /entries/{id}/touch
(debounced 2s), sidebar Tools entry showing top-10 by accessed_at.
- Encrypted attachments: per-entry file storage (5MB cap), AES-GCM
with vault key, native Save As download, paperclip upload in
slideover. New entry_attachments table + PM.Handler.Attachments.
- Password expiry: vault_entries.password_changed_at (conditional bump
via SQL CASE only when ciphertext differs), passwordExpiryDays
setting, "Aged" badge on cards + matching Filters chip.
- Recovery: Print button on generated code modal (A4 printable sheet
via @media print, code in 32px monospace + instructions).
- Audit log viewer (sidebar Tools, GET /audit with pagination cursor).
- Plaintext CSV export + Filters dropdown with 9 predicates.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|