mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-08-01 20:12:11 +00:00
* test(migration): wait for migrated state persistence * fix(sync): accept legacy singleton LWW operations Treat payload IDs as canonical only for adapter-backed LWW targets so legacy time-tracking operations can replay without weakening task retarget protection. * fix(sync): harden singleton LWW replay Preserve compatibility payload IDs for composite singleton conflicts while keeping adapter IDs canonical. Ignore malformed non-record singleton payloads before they can overwrite state. * fix(sync): harden malformed LWW payload handling * fix(sync): scope malformed LWW array handling Normalize legacy numeric-key singleton payloads while preserving valid array-backed map values such as planner days. * fix(sync): drop unreproduced LWW array-spread guard; add lockstep test Review follow-up for the singleton LWW replay fix (#9256). The #9256 repro (issue log: op 019d1e73, TIME_TRACKING, entityId PROJECT:eP8tBLmm0tBgJThAZOxcT:2026-03-24) shows the failing payload is a well-formed { project, tag } record with payloadId "<undefined>" — the core isLwwPayloadIdCanonical fix already handles it. There is NO array-spread or non-record payload anywhere in the repro, so the numeric-key "legacy-array-spread-record" detector had no reproduced failure behind it and carried a latent footgun (it would zero any future singleton with all-numeric top-level keys). Remove it, per the repo rule "start from a reproducible problem": - Drop isLegacyArraySpreadRecord / isCanonicalArrayIndexKey / MAX_ARRAY_INDEX from the converter; keep the footgun-free !isRecord normalization (a bare string/array payload still no-ops). - Drop the producer's isMalformedSingletonState; basePayload again includes arrays, which preserves PLANNER (map) day-array spreading and normal singletons alike. - Remove the two tests that only exercised the deleted guard. Also (no behavior change): - Add a converter test pinning that an op whose plaintext actionType is swapped to a singleton type lands as a whole-slice singleton replace (id stripped), never a TASK retarget — gate/converter/reducer branch on the same actionType in lockstep. - Document the integrity gate's adapter-only scope, the compat-id sunset, and the un-migrated entityId==='*' check in sync-core. * test(sync): add end-to-end recovery, back-compat & classification coverage Confidence-raising tests for #9256 (no production-code change): - operation-encryption: weld the full seam in one test — real AES-GCM decrypt -> convertOpToAction -> lwwUpdateMetaReducer — proving the reporter's legacy TIME_TRACKING op actually restores { project, tag } state, not merely that it "wasn't rejected". - conflict-resolution: simulate the shipped v18.15.1 metadata-integrity gate (faithful copy, verified via git show) and assert the new producer's compatibility id makes old clients accept the op — and, non-vacuously, that stripping it reproduces the #9256 rejection. - entity-registry: exhaustive table asserting isLwwPayloadIdCanonical is true for exactly the adapter entities across all 18 configs, guarding the misclassification that caused #9256. * docs(sync): correct singleton-classification comments (#9256) Review follow-up. Comment-only — no behavior change, verified by diff. - sync-core convertLocalDeleteRemoteUpdatesToLww: the NOTE claimed "singletons never emit per-entity deletes". They do — menuTreeDeleteFolder emits MENU_TREE + OpType.Delete with a folderId entityId. The branch is still unreachable, but via a different guard: extractEntityFromPayload finds no base entity in that delete payload (no `menuTree` key, no id-matching array element, and the field is named `folderId`, not `id`). Name the real guard, since the stated one invites the exact refactor that would arm the line. - SINGLETON_ENTITY_ID docstring: no shipped singleton producer emits '*'. GLOBAL_CONFIG addresses ops by section key, MENU_TREE by tree name / folderId, TIME_TRACKING by a composite TYPE🆔date key. Point readers at isLwwPayloadIdCanonical for the storage-pattern question. - createLWWUpdateOp SUNSET note: the compat id covers every singleton, not just TIME_TRACKING, so the fleet-age cleanup is broader than the note said; the '*' else branch is unreachable in practice. - validate-operation-payload: flag the third un-migrated isSingletonEntityId site (inert — warning-only, and LWW ops carry `entityChanges: []`) so it is not the one silent site left. * test(sync): correct the non-vacuity note on the v18.15.1 gate sim (#9256) Comment-only. The old note credited the second assertion for the test's non-vacuity, but that one strips `id` from the test's own copy and re-runs the test's own helper — it can only prove the simulated predicate still discriminates. The load-bearing assertion is the first: it fails when the producer stops emitting the compat id (i.e. when `|| !isSingletonEntityId(entityId)` is dropped from createLWWUpdateOp — the SUNSET cleanup performed too early), which is exactly the regression this test exists to catch. Say so, and label the second assertion as the guard on the simulation itself. |
||
|---|---|---|
| .. | ||
| encryption | ||
| apply.types.ts | ||
| compression.ts | ||
| conflict-resolution.ts | ||
| download-planning.ts | ||
| encryption.ts | ||
| entity-frontier.ts | ||
| entity-key.util.ts | ||
| entity-registry.types.ts | ||
| error.util.ts | ||
| full-state-op-types.ts | ||
| index.ts | ||
| lww-update-action-types.ts | ||
| operation.types.ts | ||
| ports.ts | ||
| remote-apply.ts | ||
| replay-coordinator.ts | ||
| sync-file-prefix.ts | ||
| sync-import-filter.ts | ||
| sync-logger.ts | ||
| upload-planning.ts | ||
| vector-clock.ts | ||
| web-crypto-error.ts | ||