The trash is a soft delete: no tombstone is written and the rows are absent from state.entries, so the merge read their uuids as missing locally and POSTed them back as fresh live entries. Trashing 30 entries then syncing produced 30 live duplicates alongside the trashed originals - two rows sharing one uuid.
applyRemoteSnapshot now loads the trash and skips any uuid found there. Emptying the trash is what writes the tombstone that propagates the delete.
A renderer dump taken after a quick-unlock, with nothing opened, contained the auto-backup password in cleartext - inside an ExecuteJavaScript string and in the DevTools protocol buffers - and it survived the auto-lock that wipes the vault key.
New prefs/has answers set-or-not without moving the value. The settings panel uses that; only the paths that encrypt a backup, test the WebDAV connection or run a sync fetch the secrets themselves.
A pinned entry came back unpinned after export→import. Wider than pinned:
`favorite` was WRITTEN into the export/backup/sync containers but nothing
ever read it back — not parseEntriesFromJSON, not encryptImportEntry, not
the server INSERTs. So it was a write-only field, and a restore silently
dropped both flags. `pinned` wasn't in any container at all.
Wired the full round-trip:
- containers: export + auto-backup now carry `pinned` (favorite was there).
- parseEntriesFromJSON reads both back (absent → 0, e.g. foreign CSV).
- encryptImportEntry passes them through to the body.
- server create + bulk-import INSERTs write favorite/pinned
(GetValue<Boolean>(..., False) → 0 default). PUT still ignores them, so a
sync update can't clobber local flags.
Sync stays device-local as decided 2026-07-11: buildSyncSnapshot now OMITS
favorite explicitly (it was dead weight before; with the passthrough live,
leaving it in would have started propagating it). Commented so the intent
isn't re-broken.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to 18fe13a: the custom-icon flag now survives round-trips instead
of resetting to fetched on a restore.
- Containers write icon_custom: manual export (doExport), auto-backup, sync
snapshot; parseEntriesFromJSON carries it back (default 0 when absent, e.g.
foreign CSV).
- encryptImportEntry ships it in the body (shared by bulk-import + sync
create/update).
- Server create + bulk-import INSERTs read icon_custom
(GetValue<Boolean>('icon_custom', False) → 0 default). An empty icon always
forces icon_custom=0.
Now a custom icon stays custom after export→import and across sync — it won't
be silently reclassified as cache and clobbered by Re-fetch-all on the other
device.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
applyRemoteSnapshot swallowed attachment + folder restore errors in silent
catch blocks. Count them (attFailed/folderFailed) and warn in a toast after
the sync summary. These don't abort the push (the entry synced, only its
attachment/folder didn't) unlike a failed entry import.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sync password is the only thing protecting the remote snapshot, but it
accepted 8 chars. Gate raised to 12+ chars AND computeStrength >= 50 (reused
from app.js — no zxcvbn dependency).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The profile picture (users.avatar_b64, cosmetic/unencrypted) travelled only
in the manual export. Now it's also in buildSyncSnapshot and the auto-backup
container, so a new device / a restore picks it up.
- Restore is ADDITIVE (mirrors the import path): applyRemoteSnapshot adopts
remote.avatar_b64 only when the local device has no avatar — never clobbers
a locally-set picture. No per-avatar timestamp to arbitrate, so changing an
existing avatar doesn't propagate (cosmetic, accepted).
- +2 merge tests (adopt-when-empty, don't-clobber-when-set). 65/65 green.
- Server /avatar endpoint unchanged (already accepts {avatar_b64}).
Closes the avatar item of CODE_AUDIT §4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second slice of the app.js split (after crypto). Moves the WebDAV sync
section to js/app.sync.js: transport (_webdavCall), buildSyncSnapshot,
applyRemoteSnapshot (merge + tombstone arbitration), runSyncNow, and the
sync settings UI.
- Byte-for-byte identical to the extracted block (verified before removal);
no duplicate const; no top-level sync reference left in app.js.
- Load order: AFTER app.js (unlike crypto, which loads before) because this
module has a top-level side effect — `Bridge.onWebdavResult = …` — that
needs Bridge/state/api already declared. Rule documented in CLAUDE.md.
- index.html + BuildAssets whitelist + harness APP_PARTS updated; assets
rebuilt to embed the new file.
- Safety net: the existing merge tests exercise applyRemoteSnapshot /
buildSyncSnapshot from the extracted file and stay green (42/42).
app.js: 11936 → 11256 lines (crypto + sync now separate).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>