offsetHeight feeds --hdr-h, where the sticky pagination parks, and it ignores margins - so the 20px gap under the title vanished as soon as the header stuck. Turned into padding: measured and painted, so the spacing stays constant and opaque.
min-height still let a child grow the row, and a couple of stray pixels from the selection bar pushed the grid down in table and list view. A fixed height makes overflow purely visual - nothing inside can move what follows.
renderGrid replaces every card, so the scroll container jumped back to the top: ticking a card near the bottom of a long list threw the user back to the first row. Only the affected card and the selection bar are updated now.
It was a few pixels taller than the reservation, so selecting anything pushed the whole grid down - the shift moving the bar into the header was supposed to remove.
content-header and pagination were exempt from click-away in full, so their large empty areas were dead: clicking beside the title or next to the page buttons left the editor open. Only the actual controls are exempt now.
They accepted any drag then looked for ids in text/plain: dragging text selected in a cell produced NaN, filtered out, so the target invited a drop that could do nothing. Entry drags now carry application/x-pm-entries, mirroring the folder MIME, and all four targets check it before accepting.
Checked ids belong to the view they were made in. Carried into the trash they were LIVE entries while the trash bar offers Delete forever, so a purge could destroy rows that had never been trashed. The checkbox-less views were the visible symptom of the same problem; one rule replaces the special case.
The batch bar survived into vault health, the authenticator grid and the audit log, none of which render checkboxes - it kept offering Delete, Move to folder and Add tag on entries the user could no longer see selected.
Free-floating it either covered the cards or the sidebar navigation. Inside .toast-container it shares the corner with the toasts, and column-reverse keeps it above them.
The pill was pinned to the left edge and covered the navigation, hiding Trash. It also kept counting after Breach check disabled, the workers carrying on with one network round-trip per entry.
The pagination bar and the table header sat at or below the checkbox z-index, and at equal values the later DOM element wins - the cards come after both, so checkboxes of rows scrolling underneath were painted over them. Layers are now explicit: check 2, table header 3, pagination 4, content header 12.
Bottom-left, out of the way of the toasts, with a live count. Deliberately not the busy overlay: the scan is network-bound and can take a while, and there is no reason to lock the vault while it runs.
The dashboard reads its pwned column from state.hibpResults but its cache was computed before the scan finished, so enabling the breach check from the health view showed 0 pwned while the entry cards already carried the flag. Disabling had the mirror problem: results cleared, dashboard still counting them.
Each category is capped, so past the cap fixing one more password leaves the total unchanged and the work looks wasted. The breakdown lists count and points per category, marks a capped deduction with (max), and explains in a tooltip why the score is standing still.
G3 invalidates the cache on every mutation, so a stale health list can no longer be staged. Also records why the score moves in steps of 10 and the per-category caps that can make early fixes invisible.
inert removes the controls from focus but not the document scroll: Space, PageUp/Down, Home/End and the arrows scroll without any focused element, so the page still moved under the overlay.
The overlay covers the page, which stops the mouse but not Tab and Space. The inert attribute removes the subtree from focus, hit-testing and the accessibility tree in one go - applied to every body-level sibling except the overlay, and always cleared in hideBusy.
GetLastActivePopup returns the form itself - the common Save dialog is not an owned popup - so the code that raises it never ran. It does live on our thread with the standard #32770 class, which EnumThreadWindows finds regardless of its owner.
Logging confirmed two things worth keeping: WM_HOTKEY is dispatched during the dialog modal loop, and IsWindowEnabled detects the state correctly.
The autofill helper attaches the caller to the TARGET thread, but the dialog runs on our own thread, so the attach was a no-op and Windows kept refusing the foreground change - the dialog rose without focus. Attaching to the current foreground thread instead is what grants the call.
SetForegroundWindow is refused when the hotkey comes from another application, so the dialog was raised but never focused; ForceForegroundWindow is now exposed by PM.Bridge for that. The explanation goes through a tray balloon because our window is disabled and behind the dialog, where no toast would be seen.
Ctrl+Shift+Q and Ctrl+Shift+A call RestoreWindowFromTray before JS can refuse, so a JS guard is too late by construction. Restoring the owner of a Save As dialog stranded it behind everything and the app looked frozen while it was only disabled.
NativeModalActive tests IsWindowEnabled - Windows disables the owner for the lifetime of the modal - and brings the dialog back to front via GetLastActivePopup instead of merely refusing.
The busy guard cancelled through the path that hands the foreground back to the captured target. During an export that pushed the native Save dialog behind the other application, leaving the app disabled by a modal the user could no longer find. refocus=0 separates refusing a request from ending a user interaction.
Guarded in autofillHandleRequest, upstream of the single-match versus picker split, so it covers Ctrl+Shift+L/P/T and both outcomes: the picker would open behind the overlay, and a single-match fill would minimise our window mid-operation. Cancels the request so Delphi drops the captured target HWND.
Global hotkeys come from Win32, outside the window, so the busy overlay intercepts nothing: Ctrl+Shift+A opened the editor behind it, unreachable. Guarded in openSlideOver and openQuickSearchModal rather than at each trigger - every caller routes through them, and no save path raises the overlay.
There are two Escape handlers: a capture-phase one for the editor and the global one-surface-per-keystroke chain. Only the first was guarded, so export - which reaches the editor through the global chain - still popped the discard-confirm under the overlay.
The discard-confirm opened underneath the busy overlay, which sits at z-index 200 - blurred and half-clickable. Worse than the display: it let the user arbitrate closing the editor while a sync or a rotation was writing to the same entry.
A 404 on save meant the work had to be copied out by hand. The POST mints a fresh uuid, so this is a new entry rather than a resurrection and cannot fight the tombstone the delete left behind. Gated on status 404 AND the server message: the HTTP layer answers 404 to any request it refuses, which the status alone would misread.
The UPDATE matched nothing but the handler still answered 200 Updated, so the editor reported Saved for an edit that went nowhere - the entry had been deleted by a sync or a purge after the editor opened it. Silent data loss; the client already surfaces a failed save as an error toast.
Records the two traps that invalidate the test: any PUT rebumps updated_at, undoing a manual rollback, and created_at plays no part in the arbitration.
Same placement as the TOTP field, which buys back a column on a dense row: label | value (eye inside) | copy | menu. Pinned top-right on a multiline field so it stays clear of the resize grip.
The inline copy button stays: copy is the frequent action and burying it costs a click every time. Unlike the old lock-versus-eye pair, two ways to copy are not confusable.
The lock button sat next to the eye and both masked the value, so they read as the same control. The eye now carries the state - cyan ring when the field is stored as secret - and flipping the flag moved into a row menu alongside Remove field, one deliberate step away from the everyday reveal. The row drops from six columns to five.
The menu reuses the card kebab classes, so closing it on an outside click is already handled.
A manual resize was lost on reopen: the row is rebuilt on every open, and remembering the height would not survive a restart either since localStorage is wiped when the ephemeral port changes the origin. Deriving it from the content removes the state entirely - the field is always the right size.
The note body of a note entry is the encrypted_password column, so logins had no way to carry notes. Rather than a dedicated encrypted column and the seven call sites that come with it, this rides on custom_fields, which rotation, duplicate, export/import and sync already carry.
Both the open-time normalisation and the save-time rebuild drop any key they do not name, so multiline is carried explicitly in each - otherwise the field silently reopens single-line.