fix(app): shrink stored favorite gifs and raise their budget (#2643)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-10 18:43:42 +02:00
1 parent 5fea319f4e
commit 905af5dd5a
13 files changed
+191 -215

No files matched your search

@@ -1,6 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {Logger} from '@app/features/platform/utils/AppLogger';
import {DEFAULT_SYNCED_FIELD_MAX_ENCODED_BYTES, decideOversizePush} from '@app/features/user/state/SyncedFieldBudget';
import {verifyRoundtripStability} from '@app/features/user/state/SyncedFieldRoundtrip';
import {
createSyncedFieldMachineSnapshot,
@@ -59,6 +60,7 @@ export async function makeSyncedField<
}
const isEnabled = (): boolean => config.enabled?.() ?? true;
let machine = createSyncedFieldMachineSnapshot();
let lastPreparedBytes: number | null = null;
let ownerUserId: string | null = null;
let observedUserId: string | null = null;
const suspend = (reason: SyncedFieldFailureReason, message: string, error?: unknown): void => {
@@ -130,13 +132,20 @@ export async function makeSyncedField<
return;
}
const encodedBytes = toBinary(schema, candidate).length;
if (encodedBytes > maxEncodedBytes) {
const decision = decideOversizePush({encodedBytes, maxEncodedBytes, lastPreparedBytes});
lastPreparedBytes = encodedBytes;
if (decision === 'drop') {
logger.error(
`${tag}: payload of ${encodedBytes} bytes exceeds per-field budget of ${maxEncodedBytes}; push dropped.`,
);
transitionOnly({type: 'sync.localAlreadySynced'});
return;
}
if (decision === 'push-shrinks') {
logger.warn(
`${tag}: payload of ${encodedBytes} bytes is over the per-field budget of ${maxEncodedBytes} but smaller than the previous candidate. Pushing so removals persist.`,
);
}
const stability = verifyRoundtripStability<T, M>({
schema,
store,
@@ -204,7 +213,7 @@ export async function makeSyncedField<
processCommands();
}
await Promise.resolve();
const maxEncodedBytes = config.maxEncodedBytes ?? 65_536;
const maxEncodedBytes = config.maxEncodedBytes ?? DEFAULT_SYNCED_FIELD_MAX_ENCODED_BYTES;
const UserSettings = (await import('@app/features/user/state/UserSettings')).default;
const SessionManager = (await import('@app/features/platform/state/AuthSession')).default;
const applyDefaultsForUserChange = (userId: string): void => {
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {describe, expect, it} from 'vitest';
import {decideOversizePush} from './SyncedFieldBudget';
const MAX = 1000;
describe('decideOversizePush', () => {
it('accepts a payload inside the budget', () => {
expect(decideOversizePush({encodedBytes: 999, maxEncodedBytes: MAX, lastPreparedBytes: null})).toBe(
'within-budget',
);
expect(decideOversizePush({encodedBytes: MAX, maxEncodedBytes: MAX, lastPreparedBytes: null})).toBe(
'within-budget',
);
});
it('drops the first over-budget payload', () => {
expect(decideOversizePush({encodedBytes: 1001, maxEncodedBytes: MAX, lastPreparedBytes: null})).toBe('drop');
});
it('drops an over-budget payload that grows', () => {
expect(decideOversizePush({encodedBytes: 1200, maxEncodedBytes: MAX, lastPreparedBytes: 1100})).toBe('drop');
});
it('drops an over-budget payload that stays the same size', () => {
expect(decideOversizePush({encodedBytes: 1100, maxEncodedBytes: MAX, lastPreparedBytes: 1100})).toBe('drop');
});
it('pushes an over-budget payload that shrinks so removals persist', () => {
expect(decideOversizePush({encodedBytes: 1050, maxEncodedBytes: MAX, lastPreparedBytes: 1100})).toBe(
'push-shrinks',
);
});
it('lets a run of removals drain back under the budget', () => {
let last: number | null = null;
const decisions: Array<string> = [];
for (const bytes of [1300, 1200, 1100, 900]) {
decisions.push(decideOversizePush({encodedBytes: bytes, maxEncodedBytes: MAX, lastPreparedBytes: last}));
last = bytes;
}
expect(decisions).toEqual(['drop', 'push-shrinks', 'push-shrinks', 'within-budget']);
});
});
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
export const DEFAULT_SYNCED_FIELD_MAX_ENCODED_BYTES = 65_536;
export const FAVORITE_GIF_MAX_ENCODED_BYTES = 384 * 1024;
export type OversizePushDecision = 'within-budget' | 'push-shrinks' | 'drop';
export function decideOversizePush(args: {
encodedBytes: number;
maxEncodedBytes: number;
lastPreparedBytes: number | null;
}): OversizePushDecision {
if (args.encodedBytes <= args.maxEncodedBytes) return 'within-budget';
if (args.lastPreparedBytes != null && args.encodedBytes < args.lastPreparedBytes) return 'push-shrinks';
return 'drop';
}