refactor(calendar): generalize the sync engine off Google (#1393) - #1423
Draft
h4yfans wants to merge 1 commit into
Draft
refactor(calendar): generalize the sync engine off Google (#1393)#1423h4yfans wants to merge 1 commit into
h4yfans wants to merge 1 commit into
Conversation
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
This was referenced Aug 13, 2026
`google/sync-service.ts` held the whole engine — source discovery, push, writeback, delete, per-source sync — in 917 lines, bound to Google by a dozen `provider: 'google'` literals, a direct `createGoogleCalendarClient()` import, and a module-global `syncInFlight` boolean shared by everything. The engine moves to `calendar/sync/` and is parameterized on a `ProviderSyncContext` — adapter factory, account routing, connection check, push toggle, default target calendar: | Was | Now | |---|---| | `discoverGoogleCalendarSources` | `discoverProviderSources` | | `syncGoogleCalendarNow` | `syncProviderNow` | | `pushSourceToGoogleCalendar` | `pushSourceToProvider` | | `deleteSourceFromGoogleCalendar` | `deleteSourceFromProvider` | | `applyGoogleCalendarWriteback` | `applyProviderWriteback` | | `applyGoogleCalendarDelete` | `applyProviderDelete` | | `syncGoogleCalendarSource` | `syncProviderSource` | Behavior rules the engine now enforces: - Writes are gated by `capabilities.supportsWrite` at the engine, not left to adapter politeness. A read-only provider cannot reach `calendar_bindings` through push, through the local-change path, or through inbound sync, and it never provisions a memrynote calendar. - `incrementalMode` drives cursor handling. Only `sync-token` / `delta-link` treat a missing cursor as invalidation — a `conditional-get` feed has no cursor to lose, so the reset branch cannot fire for it. - `ProviderGoneError` clears `sync_cursor` and re-runs the source. Google's plain `status: 410` shape is still recognized, so its behavior is unchanged. - `syncInFlight` is a per-provider set. One slow provider no longer blocks every other one. - The runner is per-provider: its own timer, resume listener, cooldown and cadence. `PUSH_BACKOFF_INTERVAL_MS` still applies to push-capable providers; `supportsPush: false` stays on the polling cadence. `FOCUS_TRIGGER_COOLDOWN_MS` and `WINDOW_FOCUS_REASON` behavior is preserved. - `ProviderRateLimitError` parks that provider until its own `retryAfterMs` elapses, without touching any other provider's schedule. - `push-conflict-retry.ts` is kept and now expresses the 412 case as `ProviderConflictError` + `If-Match`; Google's raw `status: 412` still counts. Google becomes one bound context in `providers/google/sync-service.ts`, keeping every exported name so no caller changes. `mappers.ts` moved to `sync/remote-event-mappers.ts` (it was always neutral — it maps the shared remote-event shape) with a re-export left behind, so its test never moved. The entire existing Google calendar suite passes with zero edits.
h4yfans
force-pushed
the
h4yfans/calendar-provider-registry
branch
from
August 13, 2026 16:17
36bcac9 to
f80627b
Compare
h4yfans
force-pushed
the
h4yfans/calendar-sync-engine-generalization
branch
from
August 13, 2026 16:17
302f054 to
ec745e7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1393. Phase 0 of the multi-provider calendar epic #1390.
What
google/sync-service.tsheld the whole engine in 917 lines. What bound it to Google was narrow and countable: a dozenprovider: 'google'literals, a directcreateGoogleCalendarClient()import, and a module-globalsyncInFlightboolean — one slot, shared by everything.The engine moves to
calendar/sync/and takes aProviderSyncContext(adapter factory, account routing, connection check, push toggle, default target calendar):discoverGoogleCalendarSourcesdiscoverProviderSourcessyncGoogleCalendarNowsyncProviderNowpushSourceToGoogleCalendarpushSourceToProviderdeleteSourceFromGoogleCalendardeleteSourceFromProviderapplyGoogleCalendarWritebackapplyProviderWritebackapplyGoogleCalendarDeleteapplyProviderDeletesyncGoogleCalendarSourcesyncProviderSourceBehavior rules, now enforced by the engine
capabilities.supportsWriteat the engine level, not left to adapter politeness. A read-only provider cannot reachcalendar_bindingsthrough push, through the local-change path, or through inbound sync — and it never provisions a memrynote calendar.incrementalModedrives cursor handling. Onlysync-token/delta-linktreat a missing cursor as invalidation. Aconditional-getfeed has no cursor to lose, so the reset branch cannot fire for it — otherwise every ICS poll would re-read from scratch forever.ProviderGoneErrorclearssync_cursorand re-runs the source. Google's plainstatus: 410shape is still recognized, so today's behavior is bit-for-bit unchanged.syncInFlightis a per-provider set. One slow provider no longer blocks every other one.PUSH_BACKOFF_INTERVAL_MSstill applies to push-capable providers;supportsPush: falsestays on the polling cadence.FOCUS_TRIGGER_COOLDOWN_MSandWINDOW_FOCUS_REASONbehavior is preserved exactly.ProviderRateLimitErrorparks that provider until its ownretryAfterMselapses, without touching any other provider's schedule.push-conflict-retry.tsis kept and expresses the 412 case asProviderConflictError+If-Match; Google's rawstatus: 412still counts.How the Google suite stayed untouched
Rather than rewrite the Google tests, Google is expressed as one bound context in
providers/google/sync-service.tsthat keeps every exported name and signature.google-sync-runner.tsdoes the same over the generic runner.mappers.tsmoved tosync/remote-event-mappers.ts— it was always provider-neutral, it maps the shared remote-event shape — with a re-export left at the old path somappers.test.tsdid not even move.Result: the entire existing Google calendar suite passes with zero edits — no assertion, no mock, no import path. That is the correctness proof the issue asked for.
engine.tscrossed the 800-linemax-lineslimit, so the inbound half (applyProviderWriteback/applyProviderDeleteand their publishers) is split intosync/writeback.ts, the same waypush-conflict-retry.tswas split out originally.Verification
pnpm typecheck— 16/16 greenpnpm lint— 0 errorsshared2455 ·main6363 passed / 1 expected fail / 4 skipped (517 files) ·main-integration9 ·renderer6965 passed / 6 skippedNew
sync/engine.test.ts(9 tests) andsync/runner.test.ts(6 tests) against an in-memory fake adapter, covering exactly the issue's checklist:supportsWrite: falseadapter produces nocalendar_bindingsrow under any path (direct push, local-change path, inbound sync), and no memrynote calendarProviderGoneErrorresets the cursor and re-runs withsyncCursor: nullsync-tokenprovider but is normal forconditional-getProviderRateLimitErrormakes the runner waitretryAfterMs, skipping periodic polls inside the window and resuming after it, without throttling any other providerOne pre-existing test changed:
inbox/sync-enqueue-guard.test.tsis a tripwire listing every module that writes inbox triage state, and theinbox_snoozewriteback moved fromcalendar/providers/google/sync-service.tstocalendar/sync/writeback.ts. The guard caught the move — exactly its job — so its expected path was updated. No assertion logic changed.Docs
MEMRY_DOCS_IMPACT_SKIP=1on push: no user-facing change. Google is still the only provider, the cadence and cooldowns are identical, and no setting, schema or channel moved. Docs land with #1394 and #1396.