Shape of an offline sync handler, as registered on GeoLeaf.Sync.
All members are optional: the engine guards every call (handler.processSyncQueue?.()),
so a partially-implemented handler is safe. A data plugin that owns an offline flow
pushes its handler at its own entry.ts — the core never imports a plugin, which is
what keeps no-plugin-in-core true once the engine lives in-core.
🛑 It lives in the CONTRACT because it had two declarations, and they lied in
concert (compteur C4, soldé par 4.9). kernel/shared/sync-handler-seam.ts held one
and offline-ui/src/core/sync-seam.ts the other; both typed restoreBackup(backupId: string) while the sync_backups store is autoIncrement, so its keys are numeric.
The typecheck was green on both sides for a call that never found anything. Two
declarations of one contract do not drift apart loudly — they agree, and are both
wrong. The plugin now reads this type through the types-only subpath
@geoleaf/core/contracts/sync.contract.js: erased at build, no runtime coupling.
Shape of an offline sync handler, as registered on
GeoLeaf.Sync.All members are optional: the engine guards every call (
handler.processSyncQueue?.()), so a partially-implemented handler is safe. A data plugin that owns an offline flow pushes its handler at its ownentry.ts— the core never imports a plugin, which is what keepsno-plugin-in-coretrue once the engine lives in-core.🛑 It lives in the CONTRACT because it had two declarations, and they lied in concert (compteur C4, soldé par 4.9).
kernel/shared/sync-handler-seam.tsheld one andoffline-ui/src/core/sync-seam.tsthe other; both typedrestoreBackup(backupId: string)while thesync_backupsstore isautoIncrement, so its keys are numeric. The typecheck was green on both sides for a call that never found anything. Two declarations of one contract do not drift apart loudly — they agree, and are both wrong. The plugin now reads this type through thetypes-only subpath@geoleaf/core/contracts/sync.contract.js: erased at build, no runtime coupling.