style: apply prettier formatting

This commit is contained in:
Johannes Millan 2026-01-10 17:09:14 +01:00
parent ce91c8b1dc
commit 038a722ed8
5 changed files with 36 additions and 69 deletions

View file

@ -47,7 +47,6 @@ npm run test:file <filepath>
- Unit tests: `npm test` - Uses Jasmine/Karma, tests are co-located with source files (`.spec.ts`)
- E2E tests: `npm run e2e` - Uses Playwright, located in `/e2e/tests/`
- `npm run e2e` - Run all tests with minimal output (shows failures clearly)
- `npm run e2e:file <path>` - Run a single test file with detailed output
- Example: `npm run e2e:file tests/work-view/work-view.spec.ts`

View file

@ -401,13 +401,11 @@ When encryption is enabled on one client:
## Testing Strategy
1. **Unit tests** for `OperationEncryptionService`
- Encrypt/decrypt round-trips with various payload types
- Non-encrypted ops pass through unchanged
- Wrong password throws DecryptError
2. **Integration tests** for upload/download
- Encrypted operations sync correctly
- Mixed encrypted/unencrypted history works
- Piggybacked operations decrypt correctly

View file

@ -507,12 +507,10 @@ All phases have been implemented as of December 2025:
### ✅ Phase 1: Core Infrastructure (Complete)
1. **Types** (`operation.types.ts`):
- `HybridManifest`, `SnapshotReference`, `OperationFileReference` interfaces defined
- Backward compatibility maintained with existing `OperationLogManifest`
2. **Manifest Handling** (`operation-log-manifest.service.ts`):
- `loadManifest()` handles v1 and v2 formats
- Automatic v1 to v2 migration on first write
- Buffer/overflow logic in upload services
@ -524,7 +522,6 @@ All phases have been implemented as of December 2025:
### ✅ Phase 2: Snapshot Support (Complete)
4. **Snapshot Operations** (in `operation-log-upload.service.ts` and `operation-log-download.service.ts`):
- Snapshot generation with current state serialization
- Upload with retry logic
- Download + validate + apply
@ -536,7 +533,6 @@ All phases have been implemented as of December 2025:
### ✅ Phase 3: Robustness (Complete)
6. **Concurrency Control**:
- Provider-specific revision checking (Dropbox rev, WebDAV ETag)
- Retry-on-conflict logic implemented

View file

@ -912,19 +912,16 @@ Remote Op (v1) Local Op (v2)
When deploying a schema migration:
1. **Release new version with migration code**
- Add migration to `MIGRATIONS` array
- Bump `CURRENT_SCHEMA_VERSION`
- Migration handles both state and operations
2. **Graceful degradation period**
- Old clients continue working (they don't know about new schema)
- New clients migrate incoming old ops seamlessly
- Mixed-version sync works via receiver-side migration
3. **Monitoring** (future)
- Track `op.schemaVersion` distribution in server logs
- Alert if many clients are > 2 versions behind
@ -988,13 +985,11 @@ export const MIGRATIONS: SchemaMigration[] = [
Before releasing any migration:
1. **Unit tests** in `schema-migration.service.spec.ts`:
- State migration correctness
- Operation migration correctness
- Null return for dropped operations
2. **Integration tests** in `cross-version-sync.integration.spec.ts`:
- Client A (v1) syncs with Client B (v2)
- Both clients converge to same state
- No data loss during migration
@ -1729,19 +1724,16 @@ When IndexedDB storage quota is exceeded, the system handles it gracefully:
**Implementation** (see `operation-log.effects.ts`):
1. **Error Detection**: Catches `QuotaExceededError` including browser variants:
- Standard: `DOMException` with name `QuotaExceededError`
- Firefox: `NS_ERROR_DOM_QUOTA_REACHED`
- Safari (legacy): Error code 22
2. **Emergency Compaction**: Triggers `emergencyCompact()` with shorter retention:
- Normal retention: 7 days (`COMPACTION_RETENTION_MS`)
- Emergency retention: 24 hours (`EMERGENCY_COMPACTION_RETENTION_MS`)
- Only deletes ops that have been synced (`syncedAt` set)
3. **Circuit Breaker**: Flag `isHandlingQuotaExceeded` prevents infinite retry loops:
- If quota exceeded during retry attempt, aborts immediately
- Shows error to user instead of looping forever

View file

@ -286,9 +286,8 @@ describe('Storage Quota Cleanup', () => {
describe('deleteOldestRestorePointAndOps', () => {
it('should return failure when no restore points exist', async () => {
// Import after mocks are set up
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -305,9 +304,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should delete oldest restore point and all ops before it when 2+ restore points exist', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -330,9 +328,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should keep single restore point but delete ops before it', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -353,9 +350,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should return failure when restore point is at seq 1 with no ops before it', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -375,9 +371,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should handle BACKUP_IMPORT as restore point', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -393,9 +388,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should handle REPAIR as restore point', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -413,9 +407,8 @@ describe('Storage Quota Cleanup', () => {
describe('Storage quota with auto-cleanup', () => {
it('should recalculate storage when quota check fails', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -434,9 +427,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should allow upload after storage recalculation shows actual usage is lower', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -457,9 +449,8 @@ describe('Storage Quota Cleanup', () => {
describe('freeStorageForUpload - iterative cleanup', () => {
it('should return success immediately if quota is already satisfied', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -480,9 +471,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should return failure when no restore points exist and quota exceeded', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -507,9 +497,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should return failure when only one restore point exists', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -535,9 +524,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should delete multiple restore points iteratively until quota is satisfied', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -597,9 +585,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should stop when only one restore point remains even if quota still exceeded', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -636,9 +623,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should return stats even when cleanup fails', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();
@ -667,9 +653,8 @@ describe('Storage Quota Cleanup', () => {
describe('Stale snapshot cache cleanup', () => {
it('should clear snapshot cache when deleted ops include the cached snapshot seq', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
const { prisma } = await import('../src/db');
initSyncService();
const service = getSyncService();
@ -708,9 +693,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should NOT clear snapshot cache when cached seq is after deleted ops', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
const { prisma } = await import('../src/db');
initSyncService();
const service = getSyncService();
@ -749,9 +733,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should clear snapshot cache when cached seq equals deleteUpToSeq exactly', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
const { prisma } = await import('../src/db');
initSyncService();
const service = getSyncService();
@ -789,9 +772,8 @@ describe('Storage Quota Cleanup', () => {
});
it('should not crash when no cached snapshot exists', async () => {
const { initSyncService, getSyncService } = await import(
'../src/sync/sync.service'
);
const { initSyncService, getSyncService } =
await import('../src/sync/sync.service');
initSyncService();
const service = getSyncService();