From 714aabda6d6b6871d4f874f6d5a3f0f53f8b5e7d Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 00:45:55 +0000 Subject: [PATCH] fix(web): stop throwing when right-clicking an uncached event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Right-clicking a grid event whose id isn't in the react-query cache threw an uncaught "Selected event not found" from getSelectedEvent and took down the grid. The DOM id (data-event-id) can legitimately disagree with the cache: unsaved drafts render with draft.clientId, week transitions render from non-cache-backed placeholderData, and optimistically removed events can still be on screen for a frame. Make the miss a no-op — getDraftForEvent returns null and handleContextMenu bails, matching every other findEventInCache caller. Also fall back to the current grid draft when the clicked id matches getGridDraftId(draft), so right-clicking an in-progress draft opens the menu instead of doing nothing. Generated-By: PostHog Code Task-Id: 9ba7ed8a-80a1-49ac-b763-82a2090da9d7 --- .../ContextMenu/GridContextMenuWrapper.tsx | 25 +++++++++++++------ .../ContextMenu/contextMenuLayering.test.tsx | 19 ++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/packages/web/src/components/ContextMenu/GridContextMenuWrapper.tsx b/packages/web/src/components/ContextMenu/GridContextMenuWrapper.tsx index 7a612d30bd..73f058c04e 100644 --- a/packages/web/src/components/ContextMenu/GridContextMenuWrapper.tsx +++ b/packages/web/src/components/ContextMenu/GridContextMenuWrapper.tsx @@ -3,8 +3,10 @@ import { useQueryClient } from "@tanstack/react-query"; import type React from "react"; import { useState } from "react"; import { getCalendarEventIdFromElement } from "@web/common/utils/event/event.util"; +import { type GridEventDraft } from "@web/events/event-draft.types"; import { editGridEventDraft, + getGridDraftId, gridEventDraftToGridEvent, } from "@web/events/grid-event-draft.adapter"; import { findEventInCache } from "@web/events/queries/event.query.cache"; @@ -46,14 +48,23 @@ export const ContextMenuWrapper = ({ }, }); - const getSelectedEvent = (eventId: string) => { - const selectedEvent = findEventInCache(queryClient, eventId); - - if (!selectedEvent) { - throw new Error("Selected event not found"); + const getDraftForEvent = (eventId: string): GridEventDraft | null => { + // The id comes from the DOM (`data-event-id`), which can legitimately + // disagree with the react-query cache: an in-progress grid draft renders + // with `draft.clientId`/its edited source id (never cached), so match it + // directly rather than looking it up. + if (gridDraft && getGridDraftId(gridDraft) === eventId) { + return gridDraft; } - return selectedEvent; + // A cache miss is an ordinary outcome (unsaved drafts, non-cache-backed + // placeholderData during week navigation, optimistically removed events + // still in the DOM for a frame). Treat it as a no-op — like every other + // `findEventInCache` caller — instead of throwing and taking down the grid. + const selectedEvent = findEventInCache(queryClient, eventId); + if (!selectedEvent) return null; + + return editGridEventDraft(selectedEvent); }; const handleDiscard = () => { @@ -73,7 +84,7 @@ export const ContextMenuWrapper = ({ if (hasClickedOnEvent) { e.preventDefault(); - const draft = editGridEventDraft(getSelectedEvent(eventId)); + const draft = getDraftForEvent(eventId); if (!draft) return; refs.setReference(cursorReference(e.clientX, e.clientY)); diff --git a/packages/web/src/components/ContextMenu/contextMenuLayering.test.tsx b/packages/web/src/components/ContextMenu/contextMenuLayering.test.tsx index cc14ca0285..ffab1800de 100644 --- a/packages/web/src/components/ContextMenu/contextMenuLayering.test.tsx +++ b/packages/web/src/components/ContextMenu/contextMenuLayering.test.tsx @@ -92,6 +92,25 @@ describe("context menu layering", () => { expectMenuFloatsAboveTheGrid(); }); + // Right-clicking a card whose id isn't in the cache (unsaved drafts, + // week-transition placeholderData, optimistically removed events) used to + // throw an uncaught "Selected event not found" and take down the grid. + // Now the miss is a no-op: no throw, no menu. + it("ignores a right-click on an event that isn't in the cache", () => { + render( + +
Orphan event
+
, + { queryClient: createCompassQueryClient() }, + ); + + expect(() => + fireEvent.contextMenu(screen.getByText("Orphan event")), + ).not.toThrow(); + + expect(document.querySelector(".c-context-menu")).toBeNull(); + }); + // The day view builds its own menu around the same component. It used to // lean on a z-index baked into the shared stylesheet, so removing that // silently dropped it behind every card.