feat: WebDAV sync + batch DnD + clean shutdown + center-modal UX bundle

- Sync (WebDAV, auto-merge): UUID + tombstones foundations (server +
  JS), THTTPClient bridge cmds (get/put/test), runSyncNow engine with
  pull/merge/push flow, Settings UI, pre-sync backup option. Test
  connection now treats 404 as OK (snapshot not yet created) and 401/
  403 as auth failure with dedicated toast.
- Batch drag-drop: cards + table rows carry checked-set ids (CSV) when
  dragged from an active selection; folder + trash drop handlers parse
  and apply in batch via new moveEntriesToFolder helper that preserves
  TOTP / custom_fields / kind in the full PUT payload.
- Clean shutdown: WM_QUERYENDSESSION / WM_ENDSESSION captured in the
  bridge message-only window; FormCloseQuery bypasses the tray-minimize
  intercept on system shutdown / restart / logoff so FireDAC closes the
  SQLite WAL cleanly instead of leaving -shm / -wal residue after a
  force-kill.
- Center-mode modal: blur+dim backdrop via body::before pseudo-element
  in editor-position=center, swallows clicks below the panel so the
  existing outside-click handlers reliably dismiss the slideover /
  settings panel.
- Batch bar state fixes: state.checked cleared before render in
  moveEntriesToFolder, emptyTrash, and per-card restoreEntry /
  permanentDelete / deleteEntry so the action bar disappears once the
  selection is fully processed.
- Save-then-discard duplicate fix: soState reset to null before
  openSlideOver re-opens the freshly saved entry, otherwise the dirty
  check fired on the soState.id=null → newId switch and a Cancel left
  the form in new-entry mode (second Save → POST duplicate).
- TEST_SYNC.md: end-to-end checklist for validating the WebDAV sync
  with 2 real instances.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-06-30 00:32:12 +01:00
parent b00da43ab0
commit 6869b7c692
10 changed files with 1132 additions and 25 deletions
+51
View File
@@ -646,6 +646,57 @@
</div>
</div>
<div class="slideover-field" id="syncField" style="display:none">
<div class="slideover-field-label">Sync (WebDAV)</div>
<p style="font-size:11px;color:var(--text-faint);margin:0 0 8px;line-height:1.5">
Bidirectional sync with a WebDAV server (Nextcloud,
ownCloud, Apache mod_dav). Auto-merge by entry
timestamp. <b>Master password is never sent to the
remote</b> — the snapshot is encrypted with a separate
sync password you set once per device (same on each).
</p>
<label style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
<input type="checkbox" id="settingSyncEnabled">
<span>Enable sync</span>
</label>
<div id="syncConfig" style="display:none;border-left:2px solid var(--border);padding-left:10px;margin-left:4px">
<div style="margin-bottom:6px">
<div style="font-size:11px;color:var(--text-faint);margin-bottom:4px">WebDAV URL (full path to the snapshot file)</div>
<input type="text" id="syncUrl" class="so-input"
placeholder="https://cloud.example.com/remote.php/dav/files/USER/PMServer/vault-sync.json"
style="width:100%;font-size:12px">
</div>
<div style="display:flex;gap:8px;margin-bottom:6px">
<label style="flex:1">
<div style="font-size:11px;color:var(--text-faint);margin-bottom:4px">Username</div>
<input type="text" id="syncUser" class="so-input" style="width:100%">
</label>
<label style="flex:1">
<div style="font-size:11px;color:var(--text-faint);margin-bottom:4px">Password / app token</div>
<input type="password" id="syncPwd" class="so-input" style="width:100%">
</label>
</div>
<label style="display:flex;align-items:center;gap:8px;margin:8px 0">
<input type="checkbox" id="syncPreBackup">
<span style="font-size:12px">Create a local backup before each sync</span>
</label>
<p style="font-size:11px;color:var(--text-faint);margin:0 0 8px;line-height:1.4">
Sync password ≠ master password. <b>Use the same one
on every device</b> — that's how the encrypted snapshot
round-trips. Stored DPAPI-protected locally.
</p>
<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-bottom:6px">
<button class="btn btn-ghost btn-sm" id="syncSetPwdBtn">Set sync password</button>
<span id="syncPwdStatus" style="font-size:11px;color:var(--text-faint)">Not set.</span>
</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:6px">
<button class="btn btn-ghost btn-sm" id="syncTestBtn">Test connection</button>
<button class="btn btn-primary btn-sm" id="syncNowBtn">Sync now</button>
<span id="syncLast" style="font-size:11px;color:var(--text-faint)"></span>
</div>
</div>
</div>
<div class="slideover-field" id="autoBackupField" style="display:none">
<div class="slideover-field-label">Auto-backup</div>
<p style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">