Skip to content

feat(calendar): provider registry and a generic calendar IPC surface (#1392) - #1422

Draft
h4yfans wants to merge 1 commit into
h4yfans/calendar-provider-adapter-interfacefrom
h4yfans/calendar-provider-registry
Draft

feat(calendar): provider registry and a generic calendar IPC surface (#1392)#1422
h4yfans wants to merge 1 commit into
h4yfans/calendar-provider-adapter-interfacefrom
h4yfans/calendar-provider-registry

Conversation

@h4yfans

@h4yfans h4yfans commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Closes #1392. Phase 0 of the multi-provider calendar epic #1390.

Stacked on #1407 (#1391). Base is h4yfans/calendar-provider-adapter-interface; review the diff against that branch, not main.

What

main/ipc/calendar-handlers.ts hard-rejected any non-Google provider in four places. The contract was already provider: z.string().min(1) rather than an enum, so this extends without breaking it.

Registrycalendar/provider/registry.ts defines CalendarProviderDefinition (id, capabilities, createAdapter, connect, disconnect, account/auth queries, discovery, sync, runner control) plus registerProvider / getProvider / listProviders. providers/google/provider-definition.ts implements it over the existing Google modules; provider/builtin.ts does the registration.

The four guards become registry lookups. The rejection string is byte-identical to before — Unsupported calendar provider: <id> — produced by one shared unsupportedProviderMessage() so no call site can drift, and asserted verbatim in tests.

Source ids. The connect handler composes ${providerId}-account: / ${providerId}-calendar:, which for google produces exactly the strings the literals produced. Existing rows keep matching; no migration.

Channels — legacy ones are kept as permanent aliases:

New Legacy (kept)
calendar:list-provider-calendars calendar:list-google-calendars
calendar:set-default-provider-calendar calendar:set-default-google-calendar
calendar:retry-source-sync calendar:retry-google-source-sync
settings:{get,set}CalendarProviderSettings settings:{get,set}CalendarGoogleSettings

Plus calendar:list-providers, so the settings page is fed from main instead of a hardcoded renderer list.

CalendarProviderStatus gains capabilities: CalendarProviderCapabilities | null. Null means "this build does not know that provider id" — which is exactly what an older client sees for a provider added later. The capability model itself moved into packages/contracts/src/calendar-api.ts (the renderer needs it) and provider/adapter.ts re-exports it under the main-process names.

Why the legacy channels are permanent

During a partial update an older renderer talks to a newer main. Deleting a channel breaks the app for the length of that window. They are a compatibility surface, not dead code — a comment says so at each definition site, and a test asserts each one still resolves to the same handler as its neutral twin.

Scope notes

  • The renderer is untouched, per the issue.
  • settings:{get,set}CalendarProviderSettings currently reads and writes calendar.<providerId>, which for google is the same calendar.google row it always was. The per-provider schema split is [calendar][P0] Per-provider credential storage + settings namespace #1394's job.
  • The provider-neutral operation names were added to the googleIntegrationOperations exclusion list in agent-mcp-channels.test.ts. They reach the same connected accounts as their Google-named twins, so allowlisting one would re-open the Workspace Limited Use hole the original exclusion closes.
  • Telemetry event names stay Google-shaped (calendar_google_connected) with a pointer to [calendar][P5] Multi-provider telemetry, docs, and epic close-out #1406; google is still the only registered provider, so nothing changes for users.

Verification

  • pnpm typecheck — 16/16 tasks green
  • pnpm lint — 0 errors
  • pnpm ipc:generate && pnpm ipc:check — green (bindings regenerated)
  • pnpm check:architecture / pnpm check:contracts — green
  • @memry/contracts — 50 files, 1655 tests passed
  • Desktop, per project: shared 2455 · main 6348 passed / 1 expected fail / 4 skipped · main-integration 9 · renderer 6965 passed / 6 skipped

New tests in calendar-handlers.test.ts: calendar:list-providers reports google with its capabilities; connect/disconnect/refresh each reject an unregistered provider with the exact historical string and a capabilities: null status; an unregistered provider never reaches a Google flow; each legacy channel resolves to the same handler as its neutral twin; every registered channel is unregistered. Plus provider/registry.test.ts for lookup, isolation between two registered providers, and the message.

Three existing status assertions gained the new capabilities field — asserted against GOOGLE_CAPABILITIES from the definition rather than a copied literal, so a capability flip has to be a deliberate one-place edit.

Docs

MEMRY_DOCS_IMPACT_SKIP=1 on push: no user-facing change. Google is still the only registered provider and every surface behaves identically; the new channels have no UI yet (that is #1395). Docs land with #1394 and #1396.

@github-actions github-actions Bot added enhancement New feature or request test labels Aug 13, 2026
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit f80627b.

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.70073% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...n/calendar/providers/google/provider-definition.ts 88.23% 4 Missing ⚠️
apps/desktop/src/main/ipc/calendar-handlers.ts 94.52% 4 Missing ⚠️
apps/desktop/src/main/ipc/settings-handlers.ts 71.42% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

…1392)

`calendar-handlers.ts` hard-rejected any non-Google provider in four places,
and four channel names were nailed to Google. The contract was already
`provider: z.string().min(1)` rather than an enum, so this extends without
breaking it.

New `calendar/provider/registry.ts`: a `CalendarProviderDefinition` carrying
`id`, `capabilities`, `createAdapter`, `connect`, `disconnect`, account and
auth queries, discovery, sync and runner control. `providers/google/
provider-definition.ts` implements it over the existing Google modules;
`provider/builtin.ts` registers it.

The four `input.provider !== 'google'` guards become registry lookups. An
unregistered provider gets the byte-identical message it got before —
`Unsupported calendar provider: <id>` — via one shared
`unsupportedProviderMessage()` so no call site can drift.

The connect handler now composes source ids as `${providerId}-account:` /
`${providerId}-calendar:`, which produces exactly the strings the google
literals produced, so existing rows keep matching.

New channels, with the Google-named originals kept as permanent aliases:

| New | Legacy (kept) |
|---|---|
| `calendar:list-provider-calendars` | `calendar:list-google-calendars` |
| `calendar:set-default-provider-calendar` | `calendar:set-default-google-calendar` |
| `calendar:retry-source-sync` | `calendar:retry-google-source-sync` |
| `settings:{get,set}CalendarProviderSettings` | `settings:{get,set}CalendarGoogleSettings` |

Also `calendar:list-providers`, so the settings page is fed from main instead
of a hardcoded renderer list, and `CalendarProviderStatus.capabilities` so the
renderer can hide write/push affordances. `capabilities` is null for a provider
this build does not know — which is what an older client sees for a provider
added later.

The legacy channels are a compatibility surface, not dead code: during a
partial update an older renderer talks to a newer main, and deleting a channel
breaks the app for the length of that window. Comments say so at every
definition site.
@h4yfans
h4yfans force-pushed the h4yfans/calendar-provider-registry branch from 36bcac9 to f80627b Compare August 13, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant