fix(desktop): stop pushing a snapshot that changed nothing
Measured on the phone, opening a cached vault: 121 ms to read the sealed file, 14 ms to display it - and then 5718 ms decrypting a snapshot it already had. The cause is upstream. A sync pushes unconditionally at the end, and the push is never a no-op: the container is re-encrypted with a fresh salt and IV, so an identical vault produces completely different bytes. On Gitea that is one more commit and a new blob sha, so every other device sees a "new" snapshot and pays a full key derivation to arrive back where it started. It also grew the repository on every sync that changed nothing, which is the history cost the plan warns about. The merged state is now compared against what was pulled, as plaintext - the only place equality is observable - ignoring snapshot_at, which changes by construction. Equal means the push is skipped and the sync still counts as done, timestamp included: having nothing to write is a success, not an absence. The comparison is deliberately strict. A false negative costs one commit; a false positive would silence a push that carried a change and lose it for the other devices. Four tests fix where that line sits, including a modified updated_at (which arbitrates the merge elsewhere) and a tombstone (a deletion that has to propagate). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
// L'egalite de deux instantanes, qui decide si un push a lieu.
|
||||
//
|
||||
// POURQUOI C'EST TESTE. Un faux positif est le cas dangereux : il ferait taire
|
||||
// un push qui portait une modification, et la perdrait pour les autres
|
||||
// appareils. La comparaison est donc volontairement stricte, et ces cas fixent
|
||||
// ou est la limite. Un faux negatif ne coute qu'un commit inutile.
|
||||
//
|
||||
// `snapshot_at` doit etre ignore : il change a chaque construction, et sans cela
|
||||
// deux instantanes identiques ne seraient jamais egaux - le correctif ne
|
||||
// servirait a rien.
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { loadApp } = require('./harness');
|
||||
|
||||
function snap(extra) {
|
||||
return Object.assign({
|
||||
version: 1,
|
||||
snapshot_at: '2026-08-17T10:00:00.000Z',
|
||||
username: 'test',
|
||||
folders: [{ name: 'folder test' }],
|
||||
entries: [{ uuid: 'u1', title: 'a', updated_at: '2026-08-01T00:00:00Z' }],
|
||||
tombstones: []
|
||||
}, extra || {});
|
||||
}
|
||||
|
||||
test('syncSnapshotsEqual: identical vaults are equal despite the build time', async () => {
|
||||
const { __test: t } = await loadApp();
|
||||
const a = snap();
|
||||
const b = snap({ snapshot_at: '2026-08-17T15:36:12.000Z' });
|
||||
assert.equal(t.syncSnapshotsEqual(a, b), true);
|
||||
});
|
||||
|
||||
test('syncSnapshotsEqual: exported_at is ignored too', async () => {
|
||||
const { __test: t } = await loadApp();
|
||||
assert.equal(t.syncSnapshotsEqual(
|
||||
snap({ exported_at: '2026-01-01T00:00:00Z' }),
|
||||
snap({ exported_at: '2026-08-17T00:00:00Z' })), true);
|
||||
});
|
||||
|
||||
test('syncSnapshotsEqual: any real difference must push', async () => {
|
||||
const { __test: t } = await loadApp();
|
||||
// Une entree modifiee.
|
||||
assert.equal(t.syncSnapshotsEqual(snap(), snap({
|
||||
entries: [{ uuid: 'u1', title: 'b', updated_at: '2026-08-01T00:00:00Z' }] })), false);
|
||||
// Un horodatage d'entree plus recent, meme titre : c'est ce qui arbitre la
|
||||
// fusion chez les autres, donc ca doit voyager.
|
||||
assert.equal(t.syncSnapshotsEqual(snap(), snap({
|
||||
entries: [{ uuid: 'u1', title: 'a', updated_at: '2026-08-17T00:00:00Z' }] })), false);
|
||||
// Une entree en plus, une en moins.
|
||||
assert.equal(t.syncSnapshotsEqual(snap(), snap({ entries: [] })), false);
|
||||
// Un dossier renomme.
|
||||
assert.equal(t.syncSnapshotsEqual(snap(), snap({ folders: [{ name: 'autre' }] })), false);
|
||||
// Une pierre tombale : la suppression doit se propager.
|
||||
assert.equal(t.syncSnapshotsEqual(snap(), snap({
|
||||
tombstones: [{ uuid: 'u9', deleted_at: '2026-08-17T00:00:00Z' }] })), false);
|
||||
});
|
||||
|
||||
test('syncSnapshotsEqual: a missing side is never equal', async () => {
|
||||
const { __test: t } = await loadApp();
|
||||
// 404 distant : il n'y a rien la-bas, donc il faut ecrire.
|
||||
assert.equal(t.syncSnapshotsEqual(null, snap()), false);
|
||||
assert.equal(t.syncSnapshotsEqual(snap(), null), false);
|
||||
assert.equal(t.syncSnapshotsEqual(null, null), false);
|
||||
});
|
||||
Reference in New Issue
Block a user