Skip to content

feat(web): paint a two-color gradient accent on merged cross-account cards - #2573

Merged
tyler-dane merged 2 commits into
mainfrom
feat/merged-card-gradient-accent
Aug 4, 2026
Merged

feat(web): paint a two-color gradient accent on merged cross-account cards#2573
tyler-dane merged 2 commits into
mainfrom
feat/merged-card-gradient-accent

Conversation

@tyler-dane

Copy link
Copy Markdown
Contributor

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 optional CrossAccountDuplicate, looked up by event id via a new findCrossAccountDuplicate helper at each of the four card-rendering call sites (week timed, week all-day, day timed, day all-day) from the crossAccountDuplicates map both view-model hooks already return (#2570). Two small shared helpers, calendarAccentStyle and calendarAccentAccessibleSuffix, are used by both TimedEventCard and AllDayEventCard so 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 backgroundColor alongside 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-check and 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

tyler-dane and others added 2 commits August 4, 2026 12:06
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
tyler-dane merged commit 5a422b8 into main Aug 4, 2026
20 checks passed
@tyler-dane
tyler-dane deleted the feat/merged-card-gradient-accent branch August 4, 2026 18:34
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>
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