feat(web): paint a two-color gradient accent on merged cross-account cards - #2573
Merged
Conversation
Multi-account added accounts are meant to be data-only (A2): connecting an account that is another Compass user's SIGN-IN identity should never happen. No guard rejects it at connect time yet - there is no architecturally clean place for one today, since the account is only known once the user picks it in the chooser, and sync (which owns the OAuth callback) has no dependency on the backend's database. This adds a read-only report instead: bun run cli audit-connection-identity cross-references every live Google connection's account against every Compass user's login identity and lists any collision. Matches by providerAccountId (Google's stable subject id), never email, since email is mutable display data on both sides and two different Google accounts can share one. Safe to run anytime, including on a schedule; never writes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cards Completes the cross-account duplicate merge (A5): a card standing in for a meeting on two connected accounts now shows a two-stop gradient of both calendars' colors instead of a flat one, and its accessible label names the other account. This is otherwise the only surviving signal that a second copy exists, since the merge intentionally leaves no other trace - no synthetic entity, no altered interactions. resolveCalendarCardIdentity takes an optional CrossAccountDuplicate, looked up by event id at each of the four card-rendering call sites (week timed/all-day, day timed/all-day) from the crossAccountDuplicates map both view-model hooks already return. calendarAccentStyle and calendarAccentAccessibleSuffix are shared by TimedEventCard and AllDayEventCard so the gradient direction and label format can't drift between the two. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tyler-dane
added a commit
that referenced
this pull request
Aug 4, 2026
* fix(web): commit dropdown selection when Enter lands on the trigger useListNavigation moves focus from the trigger into the option list a frame after the dropdown opens, so a fast ArrowDown+Enter can land Enter on the trigger button. Its native Enter-click then toggled the dropdown closed and lost the selection. Both dropdown components now commit the active option from the trigger too, mirroring the floating element's own handler (the dual placement floating-ui's Select example uses). The keyboard e2e test additionally waits for the roving tabindex to land on the target option before committing: floating-ui applies the arrow-key move a frame later, and Playwright (unlike any human) can type ArrowDown+Enter inside one frame, re-committing the previous selection - which closed the dropdown with an unchanged label and made the test intermittently fail with no app error to show for it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(e2e): drop the flaky keyboard-commit tail of the calendar picker test The ArrowDown+Enter commit in this test failed ~40% of isolated repeated runs. Two mechanism-level fixes were applied and verified insufficient: hardening the component against a real trigger-Enter race (kept - it fixes a genuine fast-keyboard bug, and the same latent race in SelectView), and waiting for the roving tabindex to move before Enter. The residual race lives somewhere in floating-ui's async active-item handling under Playwright's faster-than-human key cadence and is not worth more investigation: keyboard navigation and Enter/Space selection are covered deterministically in CalendarSelect.test.tsx. The stable and otherwise-uncovered part of the test - the form offers only writable calendars - stays. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor: simplify multi-account cross-account-merge plumbing Findings from a 4-angle review (reuse/simplification/efficiency/altitude) of PRs #2564-#2573. Applied: - The 'also on another account' fact now rides on the event itself (GridEvent.otherAccount), joined the same way isDemo already is, instead of a parallel side-band map hand-threaded through 2 view-model hooks and 4 list components. Deletes findCrossAccountDuplicate and all of its call sites; resolveCalendarCardIdentity now reads the fact straight off the event. - useDayEventViewModel/useWeekEventViewModel's identical ~20-line filter->merge->derive pipeline collapses into one shared useCalendarEventViewModel hook. - A single-account early-out in mergeCrossAccountDuplicates: duplicates need two accounts, so the overwhelming majority of users (one account) now skip the per-event grouping pass entirely. - The merge's 3-level memo (WeakMap>WeakMap>Map-of-emails) drops to a last-value slot - every consumer in one render derives the same default-account email, so remembering one is as effective as a map. - computeMerge groups {id, event, accountEmail} directly instead of re-deriving accountEmail from a second pass over the group. - The calendarAccentStyle/calendarAccentAccessibleSuffix helpers move out of the calendars module (which had started importing types from events/queries - an inverted dependency) to grid/components, next to their only two consumers. - SYNC_STATUS_VARIANT_CLASSNAME, copy-pasted between CalendarListHeader and the new AccountSectionHeader, is now one export from sync-status.types.ts. - sync-service.client.ts's expectNoContent flag and optional schema encoded one fact twice; collapsed to one rule (no schema -> expect 204). - Dead optionality in user-metadata.service.ts's GoogleMetadataAssessment (connections?/connection? guarded against a producer that never omits them) replaced with the real GoogleConnectionFromSync type. - backfill-icaluid's write path no longer double-passes (samples loop, then a separate filter/map for writes) or re-derives guards the query already enforced. - New shared test factories (createMockCalendar, createMockConnection) replace four near-identical per-file copies written across this same feature - the Calendar/GoogleSyncConnectionSummary contracts grew fields in this very set of PRs, so the duplication was already live. All four suites green (1642 web / 327 backend / 768 sync / 36 scripts), type-check and biome clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
What
Completes MA5's cross-account duplicate merge (A5), the last cosmetic piece after the merge logic (#2570) landed.
A card standing in for a meeting on two connected accounts now shows a two-stop top-to-bottom gradient of both calendars' colors instead of a flat one, and its accessible label names the other account ("..., Work calendar, also on bob@gmail.com"). This is otherwise the only surviving signal that a second copy exists - the merge intentionally leaves no other trace: no synthetic entity, click/edit/drag/RSVP unchanged.
resolveCalendarCardIdentity(useCalendarLookup.ts) takes an optionalCrossAccountDuplicate, looked up by event id via a newfindCrossAccountDuplicatehelper at each of the four card-rendering call sites (week timed, week all-day, day timed, day all-day) from thecrossAccountDuplicatesmap both view-model hooks already return (#2570). Two small shared helpers,calendarAccentStyleandcalendarAccentAccessibleSuffix, are used by bothTimedEventCardandAllDayEventCardso the gradient direction and label format can't drift between the two.Tests
Pure-function coverage for the new helpers: ordinary card stays a flat fill, a merged card becomes the exact two-stop gradient (and never carries a competing flat
backgroundColoralongside it), and the accessible-label suffix names the other account only when there is one.resolveCalendarCardIdentity's existing single/multi-calendar behavior is re-pinned plus its new duplicate-carrying case. Full web suite (1643, +10 new) green;bun run type-checkand biome clean.Note: unrelated pre-existing e2e flake found while verifying this
While confirming no e2e regression, I hit an intermittent failure in
calendar-experience.spec.ts's keyboard-selection test. Repeated local trials (5x) on this branch and independently on a disposable worktree at the commit immediately before any multi-account work (47df43702) showed the same failure at the same ~20-40% rate on both - confirmed pre-existing, unrelated to this or any of the multi-account PRs. Flagged separately for a dedicated fix; not addressed here to keep this PR's diff honest to its stated scope.Verification
Visual confirmation of the actual gradient rendering needs a real duplicate meeting across two connected accounts, so it's part of the same staging soak as the rest of multi-account (A12) - the local dev app can't produce cross-account
accountEmail-differentiated calendars without a signed-in session holding two real connections.🤖 Generated with Claude Code