Introduces the Delphi 12 FMX backend (PMServer) that hosts the embedded
WebView2 vault on 127.0.0.1, and a native bridge between JS and Delphi
that wires three privacy-focused features:
1. Secure clipboard
Copying a password registers the Win32 "ExcludeClipboardContentFromMonitorProcessing"
format alongside CF_UNICODETEXT, so Win+V clipboard history never sees
the value. Auto-clears after 30s via TTimer. Bridge.copySecure() in
app.js routes all password/username/secret copy paths through the
native layer when running inside the Delphi WebView2 (falls back to
navigator.clipboard for the PHP standalone).
2. Tray icon (X-to-tray when server running)
Closing the dev panel hides both the form HWND and the TFMAppClass
per-process proxy window that owns the FMX taskbar entry — the form's
HWND alone is not the taskbar-visible one in FMX (took some iteration
to discover). Tray menu: Open, Lock vault, Quit. Clipboard is force-
cleared on minimize as extra safety. First-time minimize fires a
balloon notification so the user knows the app is still running.
3. Auto-lock on Windows session lock (Win+L)
wtsapi32.dll!WTSRegisterSessionNotification on a dedicated message-only
window. On WM_WTSSESSION_CHANGE / WTS_SESSION_LOCK, the bridge calls
ExecuteJavaScript('lockVault()'). Same path used by the tray "Lock vault"
menu item.
Bridge architecture:
- JS → Delphi via cmd:// URLs intercepted in OnBeforeNavigate
(pattern lifted from DeskInsight Monaco). Currently exposes
cmd://clipboard/copy?text=...&clear=... and cmd://clipboard/clear.
- Delphi → JS via TTMSFNCWebBrowser.ExecuteJavaScript with guarded
calls (typeof check) so the bridge degrades cleanly if app.js isn't
loaded yet.
Files:
- Source/PM.Bridge.pas (new) — TSecureClipboard + TPMBridge
- UMainForm.pas/.fmx — bridge wiring, FormCloseQuery intercept, tray
callbacks (BridgeTrayRestore / BridgeLockRequest / BridgeQuit)
- js/app.js — Bridge object, 5 navigator.clipboard sites migrated to
Bridge.copySecure with PHP-compatible fallback, Bridge.onTrayRestore
handler that resets the auto-lock timer
.gitignore extended with Delphi build artifacts (*.dcu, Win32/, Win64/,
__history/, __recovery/, *.identcache, *.dsk, *.local, etc.) so source
checkouts stay clean.
Dark theme: neutral dark grays (#0e1015 bg, #6b7280 accent). Light theme: white cards, better contrast. Cards now have subtle shadows for separation. All hardcoded rgba(59,130,246) replaced with rgba(var(--accent-rgb), ...). Arrow key handler skips when add/edit modal is open. Detail view now uses getFilteredEntries() instead of getFilteredEntries(true) to respect folder selection.
Previous approach always started from the lowest-indexed selected entry, causing the range to snap to anchor on arrow reversal. Now arrowFocus tracks the moving end; arrowAnchor stays fixed. Non-Shift arrow resets both to the new position.
Shift+arrow now uses arrowAnchor (set on first arrow press) instead of lastSelectedId (which was updated on each non-Shift move). Fixes 'only 2 entries' bug. Detail view: fixed inverted auth check for ArrowLeft/Right; general arrow handler now excludes detail view.
Use single-byte rejection sampling: generate byte, reject if >= largest multiple of charset length, then modulo. Eliminates bias from c.charAt(arr[i] % c.length).
Ctrl+N and Ctrl+T cannot be intercepted by Chrome (browser-level shortcuts). Replaced with Alt+N and Alt+T which work reliably. Updated keyboard shortcuts help.
Switched from e.code (physical key position) to e.key (character value) which is more reliable across keyboard layouts and browsers. Also removed duplicate Ctrl guard line.
document-level keydown bubbling phase is too late for browser-level shortcuts Ctrl+N (new window) and Ctrl+T (new tab). Moved handlers to a window.addEventListener('keydown', ..., true) capture-phase handler that fires before the browser chrome acts. Removed redundant document-level copies.
Moved Ctrl+N and Ctrl+T handlers before the generic Ctrl guard with their own e.preventDefault() and early return, matching the pattern used for Ctrl+A. Removed them from the old else-if chain and combined preventDefault block.
Ctrl+A was selecting page text instead of entries. Switched from e.code === 'KeyA' (layout-dependent) to e.key === 'a'. Moved handler before the generic Ctrl+key else-if chain and before the combined preventDefault block, with its own e.preventDefault() and early return. Handles both 'a' and 'A' key values.
Ctrl+A auth check was inverted (!hidden instead of hidden), preventing selection in vault. Added Delete key shortcut: moves selected entries to trash (or batch permanent delete in trash view). Added Del to keyboard shortcuts help.
Document mousedown handler was clearing selectedIds when clicking 'Yes' (button element). Since mousedown fires before click, selectedIds was empty by the time the async callback read it, causing no-op deletes but still showing the toast. Added .batch-confirm-overlay to the mousedown exclusion list so clicking the confirm dialog won't trigger clearSelection().
Replace showCenterConfirm with showBatchConfirm(btn,message,callback) that creates a transparent modal overlay (z-index 9999) to block background clicks, positions the confirm dialog beside the trigger button. Updated all callers (batchDelete, batchPermanentDelete, permanentDelete, emptyTrash) to pass the clicked button. Added CSS for batch-confirm-overlay backdrop.
Add showCenterConfirm() function for dialogs without button anchor. Replaced all standard confirm() calls in batchDelete, batchPermanentDelete, permanentDelete, emptyTrash. Batch permanent delete shows single custom confirm with entry count, no nested dialogs. permanentDelete() accepts silent param to suppress UI for batch operations. Order cleanup added to permanentDelete.
Trash drop handler now uses noToast + single message. Restore button icon changed to ♻️. Removed stale .view-toggle reference in click-outside handler. Added safety check in reorder drop handler to prevent entries being lost from order array.
Batch delete/restore now shows single toast with count instead of one per entry. delEntry() and restoreEntry() accept noToast param. Added Ctrl+A to select all visible entries. Updated keyboard shortcuts help.
Replace view toggle buttons with dropdown menu containing all 7 views. Card view: 2-column grid with bigger cards. Grouped view: entries grouped by folder with sticky headers. Detail view: single entry at a time, large text, prev/next navigation. Arrow key navigation for detail view.
Added user-select:none and -webkit-user-drag:none to #entriesContainer.
Also added a dragstart listener that prevents default for any element
that is not [draggable='true'], stopping the browser from creating drag
ghosts of selected table text/headers during rectangle selection.
The entriesContainer has no padding so cards fill the grid edge-to-edge;
there is no clickable empty space at the left/top/bottom of the card area
within entriesContainer. Moved rect-selection initiation into the
document-level mousedown handler so it fires when clicking on ANY
non-interactive element inside .vault (padding, grid gaps, etc.).
Interactive elements (buttons, inputs, folders bar, toolbar, etc.)
still clear selection as expected.
Document mousedown handler clears selection when clicking on any element
that is not an entry card, the batch bar, or an open modal/settings menu.
Works in tandem with entriesContainer rect-select handler: when clicking
inside entriesContainer on empty space the container handler runs first
(setting rectState.active=true) so the document handler skips;
when clicking outside entriesContainer the document handler fires alone
and calls clearSelection().
- Move trash button drop listener out of setupDrag() to an IIFE so it
only registers once (was accumulating listeners on every render() call,
causing multiple toast messages on single drop).
- Fix rectangle selection mousedown handler: use optional chaining on
closest() for safety with text nodes; avoid calling render() inside
mousedown (which would detach the event target's children mid-handler)
and instead just remove .selected class directly from the DOM.
- Brighter selection highlight with stronger box-shadow, background, and left-border accent
- Rectangle/band selection: click-drag on empty space to select multiple entries via intersection test
- Drag selected entries to a folder chip to move all, or to trash button to multi-delete
- Multi-drag reorder: dragging a selected entry reorders all selected together
- Table view selection now highlights individual td cells (box-shadow on tr was invisible)
- Escape cancels rectangle selection in progress
- Remove separate 'select mode' toggle and checkbox UI
- Click any entry to select it (blue highlight border)
- Shift+click for range selection between two entries
- Ctrl/Cmd+click to toggle individual entries
- Click entry background or press Escape to clear selection
- Batch action bar appears automatically when items are selected
- Selected entries get accent-color border and highlight
- Single delEntry() cleans up selectedIds
- Shortcut help updated with selection tips
- Inline password reveal on hover (controlled by showView setting, replaces eye button)
- Drag an entry card onto a folder chip to move it (no modal needed)
- Generator presets: Strong 16, Strong 20, Paranoid 32 buttons
- Favorites: star toggle button per entry, entries sort to top
- Add favorite column to vault_entries, toggle endpoint, star UI in all views
- Gold border/background for favorited entries
- Rewrite keyboard shortcuts using e.code and early preventDefault() to reliably override browser defaults
- Add ? key and toolbar button for shortcuts help modal
- Add password strength meter to register form
- Add search highlighting in all view modes (grid/list/compact/table)
- Add hash-based color coding for folder chips
- Add highlightText utility with regex escaping
- Set SQLite busyTimeout(5000) to prevent 'database is locked' on concurrent requests
- Await all loadEntries() in mutation functions to eliminate race conditions
- Remove redundant loadEntries() from batch operations