fix(sync): bump updated_at on set-icon + folder-delete reassignment
Both UPDATEs mutated a synced column without touching updated_at, so the
change rode in the sync snapshot but other devices skipped it (last-write-
wins saw "not newer"). Now both SET updated_at = datetime('now') (UTC).
- POST /entries/{id}/icon (PM.Handler.Entries)
- folder delete → entries reassigned to 'All' (PM.Handler.Folders)
accessed_at stays exempt (read timestamp, not synced); bulk "clear all
icons" stays exempt (device-local favicon cache purge). Invariant documented
in CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -868,6 +868,14 @@ le vide ET vide `FPendingURL` → écran noir permanent sur cold-start lent.
|
|||||||
nécessite `AttachThreadInput` pour cross-process.
|
nécessite `AttachThreadInput` pour cross-process.
|
||||||
- **`updated_at`** est bumpé à chaque PUT entry → sort par défaut =
|
- **`updated_at`** est bumpé à chaque PUT entry → sort par défaut =
|
||||||
`name` asc pour que modifier une entry ne change pas sa position.
|
`name` asc pour que modifier une entry ne change pas sa position.
|
||||||
|
- **Toute mutation `vault_entries` qui doit se synchroniser DOIT bumper
|
||||||
|
`updated_at`** : la sync est last-write-wins sur `updated_at`, donc un
|
||||||
|
`UPDATE` qui ne le touche pas voyage bien dans le snapshot mais est ignoré
|
||||||
|
par les autres devices (« pas plus récent »). Corrigé pour set-icône
|
||||||
|
(`POST /entries/{id}/icon`) et réassignation-sur-delete-folder
|
||||||
|
(`PM.Handler.Folders`). `accessed_at` est **exempté** exprès (timestamp de
|
||||||
|
lecture, non synced). Le bulk « clear all icons » ne bump pas non plus —
|
||||||
|
assumé device-local (purge de cache favicon).
|
||||||
- **Timestamps = UTC partout** : tout `created_at`/`updated_at`/`deleted_at`
|
- **Timestamps = UTC partout** : tout `created_at`/`updated_at`/`deleted_at`
|
||||||
écrit côté Delphi passe par `NowUTC`/`NowUTCStr` (`PM.Database`) — JAMAIS
|
écrit côté Delphi passe par `NowUTC`/`NowUTCStr` (`PM.Database`) — JAMAIS
|
||||||
`FormatDateTime(..., Now)` (heure locale). SQLite `CURRENT_TIMESTAMP` /
|
`FormatDateTime(..., Now)` (heure locale). SQLite `CURRENT_TIMESTAMP` /
|
||||||
|
|||||||
@@ -883,7 +883,10 @@ begin
|
|||||||
try
|
try
|
||||||
LQ.Connection := DB.Connection;
|
LQ.Connection := DB.Connection;
|
||||||
LQ.SQL.Text :=
|
LQ.SQL.Text :=
|
||||||
'UPDATE vault_entries SET icon_b64 = :ic ' +
|
// Bump updated_at (UTC) so the icon change wins last-write-wins on
|
||||||
|
// sync — without it the new icon_b64 rides in the snapshot but other
|
||||||
|
// devices skip it (timestamp unchanged → "not newer").
|
||||||
|
'UPDATE vault_entries SET icon_b64 = :ic, updated_at = datetime(''now'') ' +
|
||||||
'WHERE id=:id AND user_id=:uid';
|
'WHERE id=:id AND user_id=:uid';
|
||||||
LQ.ParamByName('ic').DataType := ftMemo; // long text → ftMemo on SQLite
|
LQ.ParamByName('ic').DataType := ftMemo; // long text → ftMemo on SQLite
|
||||||
if LIcon = '' then LQ.ParamByName('ic').Clear
|
if LIcon = '' then LQ.ParamByName('ic').Clear
|
||||||
|
|||||||
@@ -350,7 +350,10 @@ begin
|
|||||||
try
|
try
|
||||||
LQ.Connection := DB.Connection;
|
LQ.Connection := DB.Connection;
|
||||||
LQ.SQL.Text :=
|
LQ.SQL.Text :=
|
||||||
'UPDATE vault_entries SET folder = ''All'' ' +
|
// Bump updated_at (UTC) so the folder reassignment propagates on sync
|
||||||
|
// (last-write-wins) — otherwise other devices keep the entries in the
|
||||||
|
// just-deleted folder because the timestamp didn't move.
|
||||||
|
'UPDATE vault_entries SET folder = ''All'', updated_at = datetime(''now'') ' +
|
||||||
'WHERE user_id = :uid AND folder = :name';
|
'WHERE user_id = :uid AND folder = :name';
|
||||||
LQ.ParamByName('uid').AsInteger := LUserId;
|
LQ.ParamByName('uid').AsInteger := LUserId;
|
||||||
LQ.ParamByName('name').AsString := LName;
|
LQ.ParamByName('name').AsString := LName;
|
||||||
|
|||||||
Reference in New Issue
Block a user