Skip to content

Keep shared Google Calendar events editable - #5569

Open
3mdistal wants to merge 1 commit into
mainfrom
t3code/diagnose-shared-clip
Open

3mdistal wants to merge 1 commit into
mainfrom
t3code/diagnose-shared-clip

Conversation

@3mdistal

Copy link
Copy Markdown
Contributor

Problem

When the same Google calendar is visible through both an owner account and a reader account, Calendar could keep whichever duplicate event arrived first. If the reader copy won, an event the user owns appeared read-only and its edit and delete actions disappeared.

Approach

Choose the strongest source for a duplicated provider event: writable access first, then primary ownership, access role, and a deterministic account fallback. Preserve account identity in multi-account event IDs so subsequent reads and mutations return to the connection that supplied the winning event.

What changed

  • rank duplicate Google event sources by effective write access instead of arrival order
  • add opaque account-scoped identities for otherwise ambiguous multi-account primary events
  • route get, update, RSVP, delete, and explicit bulk mutations through the encoded account
  • reject explicit account mismatches and mixed-account bulk requests before mutation
  • preserve read-only fallback events and their source/error provenance when a writable account fails
  • add Calendar regression coverage and a user-facing changelog entry

Safety and operations

The opaque identity is limited to ambiguous multi-account results; existing single-account Google event IDs remain compatible. There are no schema changes, migrations, credential changes, or deployment steps.

Verification

  • vitest --run across the nine affected Calendar suites: 190 tests passed
  • Calendar typecheck passed
  • guard:no-silent-coercion passed
  • guard:external-result-contract passed
  • desktop and 390x844 interface checks confirmed writable events expose edit/delete controls and reader-only events remain locked, with zero browser errors
  • bounded technical review found no remaining issues

A live personal Google event was not destructively edited or deleted; provider routing and mutation rejection are covered at the action boundary.

Review focus

  • source ranking when owner and reader connections expose the same provider event
  • compatibility and parsing of account-scoped event identities
  • account binding for single and bulk mutations
  • provenance retained by the read-only fallback path

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes and found 4 potential issues 🟡

Review Details

Code Review Summary

PR #5569 changes Calendar event identity and mutation routing so duplicate Google events prefer writable sources, while opaque account-scoped IDs preserve the connection needed for reads and mutations. The ranking and explicit account-mismatch checks are a sound direction, and the added regression coverage exercises the central duplicate and bulk-account cases. This is a standard-risk business-logic change because it affects provider reads and event mutations.

Findings

  • 🟡 Four medium-severity issues need attention:
    • Account-scoped get-event failures are converted into a misleading not-found result instead of preserving auth, rate-limit, transient, or provider-error provenance.
    • Explicitly selected primary-calendar paths can still emit legacy IDs when multiple accounts are queried, allowing same-provider-ID collisions.
    • Mutation result IDs can lose the account binding after an opaque event is updated/deleted/RSVP'd, making follow-up actions ambiguous.
    • Bulk mutations can mix opaque account-scoped IDs with legacy unscoped IDs and silently apply the inferred account to the legacy IDs.

The core source-ranking approach is deterministic and correctly prioritizes writable access over arrival order. No schema, credential, or migration changes were introduced.

🧪 Browser testing: Could not verify — the dev server was healthy, but browser automation tools were unavailable and no safe multi-account fixture existed; all planned cases were reported as environment-blocked.

Comment on lines +105 to +111
const selectedClients = accountEvent
? clients.filter(
({ email: accountEmail }) =>
accountEmail.trim().toLowerCase() === accountEvent.accountEmail,
)
: clients;
for (const { email: acctEmail, accessToken } of selectedClients) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Preserve provider failures for account-scoped event lookups

When an opaque account-scoped ID restricts selectedClients to the encoded account, the existing catch still swallows every calendarGetEvent failure and the action eventually reports Event not found. A revoked token, 403, 429, or transient provider outage is not a confirmed 404; preserve the underlying error or return typed provider-error provenance for this account-scoped path.

Additional Info
Found by 1 of 3 code-review agents; confirmed against the account-restricted lookup and catch flow.

Fix in Builder

Comment on lines 1480 to +1482
calendarSource && !calendarSource.primary
? `google-${calendarSource.sourceKey}-${event.id}`
: `google-${event.id}`,
: !calendarSource && clients.length > 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Scope selected primary events by account

When calendarSourceKeys are supplied, primary source paths still take the legacy google-${event.id} branch because the account-scoped fallback only runs when !calendarSource. If two connected primary accounts expose the same provider event ID, the rows collide and get-event has no account binding to distinguish them. Use an account-scoped ID whenever multiple accounts are queried, including explicitly selected primary paths, while retaining legacy IDs only for the single-account compatibility case.

Additional Info
Found by 1 of 3 code-review agents; confirmed from the ID branch and multi-account primary-source behavior.

Fix in Builder

Comment on lines 260 to +264
const accountEmail = await resolveOwnedAccountEmail(
args.accountEmail,
resolveGoogleEventAccountEmail(args.id, args.accountEmail),
ownerEmail,
);
const googleEventId = normalizeWritableGoogleEventId(args.id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Preserve account-scoped IDs in mutation results

An opaque event ID is resolved to its account here, but the existing success/replacement paths later return google-${googleEventId}, dropping that binding. A follow-up update, delete, or RSVP using the returned ID can resolve the same provider ID against another connected account. Preserve the original opaque ID or reconstruct it with the bound account, and apply the same rule to the analogous delete, RSVP, and bulk mutation result paths.

Additional Info
Found by 1 of 3 code-review agents; the issue applies to the shared mutation-result contract across the affected actions.

Fix in Builder

Comment on lines +214 to +227
export function resolveBulkGoogleEventAccountEmail(
ids: string[],
accountEmail: string | undefined,
): string | undefined {
const accounts = new Set(
ids
.map((id) => resolveGoogleEventAccountEmail(id, accountEmail))
.filter((email): email is string => !!email)
.map((email) => email.trim().toLowerCase()),
);
if (accounts.size > 1) {
throw new Error("Bulk event ids must belong to one Google account");
}
return accounts.values().next().value ?? accountEmail;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Reject mixed scoped and legacy IDs in bulk mutations

This helper infers one account from any opaque ID and returns it for the whole batch, even when other IDs are legacy google-<provider-id> values with no account provenance. With multiple accounts exposing the same provider ID, those legacy IDs can refer to another account but will be fetched and mutated on the inferred account. Reject mixed scoped/unscoped batches or require an explicit account for every unscoped ID.

Additional Info
Found by 1 of 3 code-review agents; confirmed from the set construction and fallback return behavior.

Fix in Builder

@github-actions

Copy link
Copy Markdown
Contributor

Visual recap — generation failed

The visual recap could not be generated for this pull request. This is informational only and does not block the PR.

Diagnostic:

No plan URL: Repair changed too much of targeted file plan.mdx; expected a localized parser fix.

Agent output: Repaired recap-source.json so MDX structural newlines are valid while code-string escapes remain intact. --- ⠀ 🟢 Corrected the rejected visual recap source in place. stderr: Reading additional input from stdin...

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant