Repository: SwitchbackTech/compass (ts)

Issue:
Render calendar immediately after authenticating
Restructure the login flow so that the user can use Compass calendar immediately after successfully authenticating, while also importing their existing calendar data in the background.

### Implementation Notes
Extract the event imports from the sign up flow
- Trigger the event import as a separate request, so it does not block UI interactions.  
  - Create a new route for importing events   
- Add a subtle loading indicator to the UI to indicate that we're fetching in the background

Fetch data after import finished 
- After the backend finishes importing all events, emit the `EVENT_CHANGED` websocket notification from the server to the client.
  - See `websocket.server.ts` and `SocketProvider.tsx`
- This will trigger the frontend to refetch the current week's events, getting the user's full calendar data.

### Loading Spinner Design

<img width="1737" alt="Image" src="https://github.com/user-attachments/assets/bd5a16c3-e537-46e7-89a4-9281cf5f4b98" />

- Add tooltip that says, "Importing your calendar events in the background"
- If you have time, try to animate the spinner so that it feels dynamic. If not, we can descope that and create a separate issue.
- Spinner should disappear after the background import finishes

Pull request:
Feat render calendar after auth - ui
# What does this PR do?

This PR adds a subtle loading indicator to the UI to indicate that we're importing the user's GCAL in the background

## Description

Closes #543 - UI stories

## Manual Test

- Sign up as a new user
- Once you're logged in, confirm that a loading spinning icon appears in the sidebar bottom row
- Hover over the loading spinning icon and confirm that it has a tooltip with the text - "Importing your calendar events in the background"
- Confirm that the spinning loading icon disappears within 30 secs - indicating that the import has completed.

https://github.com/user-attachments/assets/97742e0e-6472-4b4b-938d-78f8cbe26f36




Patch:
diff --git a/packages/backend/src/auth/controllers/auth.controller.ts b/packages/backend/src/auth/controllers/auth.controller.ts
index e199c472..6aabff0e 100644
--- a/packages/backend/src/auth/controllers/auth.controller.ts
+++ b/packages/backend/src/auth/controllers/auth.controller.ts
@@ -29,6 +29,7 @@ import {
 import syncService from "@backend/sync/services/sync.service";
 import { updateGoogleRefreshToken } from "@backend/user/queries/user.queries";
 import userService from "@backend/user/services/user.service";
+import { webSocketServer } from "../../servers/websocket/websocket.server";
 import { initGoogleClient } from "../services/auth.utils";
 
 const logger = Logger("app:auth.controller");
@@ -181,6 +182,13 @@ class AuthController {
   signup = async (gUser: TokenPayload, gRefreshToken: string) => {
     const userId = await userService.initUserData(gUser, gRefreshToken);
 
+    // @TODO - create a permanent notification trigger for gCal import
+    // temporary trigger for gCal import
+    // we will not await this promise
+    new Promise((resolve) => {
+      const timeout = setTimeout(() => resolve(clearTimeout(timeout)), 5000);
+    }).then(() => webSocketServer.handleImportGCalStart(userId));
+
     return { cUserId: userId };
   };
 }
diff --git a/packages/backend/src/servers/websocket/websocket.server.ts b/packages/backend/src/servers/websocket/websocket.server.ts
index 8eb1250e..6139e2cf 100644
--- a/packages/backend/src/servers/websocket/websocket.server.ts
+++ b/packages/backend/src/servers/websocket/websocket.server.ts
@@ -3,6 +3,8 @@ import { Server as SocketIOServer } from "socket.io";
 import {
   EVENT_CHANGED,
   EVENT_CHANGE_PROCESSED,
+  IMPORT_GCAL_END,
+  IMPORT_GCAL_START,
   RESULT_IGNORED,
   RESULT_NOTIFIED_CLIENT,
 } from "@core/constants/websocket.constants";
@@ -89,6 +91,26 @@ export class WebSocketServer {
     return this.wsServer;
   }
 
+  public handleImportGCalStart(userId: string) {
+    const socketId = this.connections.get(userId);
+
+    const isClientConnected = socketId !== undefined;
+    if (!isClientConnected) return RESULT_IGNORED;
+
+    this.notifyClient(socketId, IMPORT_GCAL_START);
+    return RESULT_NOTIFIED_CLIENT;
+  }
+
+  public handleImportGCalEnd(userId: string) {
+    const socketId = this.connections.get(userId);
+
+    const isClientConnected = socketId !== undefined;
+    if (!isClientConnected) return RESULT_IGNORED;
+
+    this.notifyClient(socketId, IMPORT_GCAL_END);
+    return RESULT_NOTIFIED_CLIENT;
+  }
+
   public handleBackgroundCalendarChange(userId: string) {
     const socketId = this.connections.get(userId);
 
diff --git a/packages/backend/src/sync/controllers/sync.controller.ts b/packages/backend/src/sync/controllers/sync.controller.ts
index c45d9042..5c244c96 100644
--- a/packages/backend/src/sync/controllers/sync.controller.ts
+++ b/packages/backend/src/sync/controllers/sync.controller.ts
@@ -101,7 +101,10 @@ class SyncController {
     // We will not await this promise
     syncService
       .importFull(gcalClient, gCalendarIds, userId)
-      .then(() => webSocketServer.handleBackgroundCalendarChange(userId))
+      .then(() => {
+        webSocketServer.handleImportGCalEnd(userId);
+        webSocketServer.handleBackgroundCalendarChange(userId);
+      })
       .catch(logger.error);
 
     return res.status(204);
diff --git a/packages/core/src/constants/websocket.constants.ts b/packages/core/src/constants/websocket.constants.ts
index 45d75f5b..b8f03ac8 100644
--- a/packages/core/src/constants/websocket.constants.ts
+++ b/packages/core/src/constants/websocket.constants.ts
@@ -4,3 +4,6 @@ export const EVENT_RECEIVED = "EVENT_RECEIVED";
 
 export const RESULT_IGNORED = "IGNORED";
 export const RESULT_NOTIFIED_CLIENT = "NOTIFIED_CLIENT";
+
+export const IMPORT_GCAL_START = "IMPORT_GCAL_START";
+export const IMPORT_GCAL_END = "IMPORT_GCAL_END";
diff --git a/packages/core/src/types/websocket.types.ts b/packages/core/src/types/websocket.types.ts
index 844327c0..bd948cb5 100644
--- a/packages/core/src/types/websocket.types.ts
+++ b/packages/core/src/types/websocket.types.ts
@@ -17,6 +17,8 @@ export interface InterServerEvents {
 
 export interface ServerToClientEvents {
   EVENT_CHANGED: () => void;
+  IMPORT_GCAL_START: () => void;
+  IMPORT_GCAL_END: () => void;
 }
 
 export interface SocketData {
diff --git a/packages/web/src/common/apis/sync.api.ts b/packages/web/src/common/apis/sync.api.ts
index d19cd147..81fe4c0a 100644
--- a/packages/web/src/common/apis/sync.api.ts
+++ b/packages/web/src/common/apis/sync.api.ts
@@ -5,6 +5,10 @@ const SyncApi = {
     const response = await CompassApi.post(`/sync/stop-all`);
     return response;
   },
+
+  async importGCal() {
+    return CompassApi.post<void>(`/sync/import-gcal`);
+  },
 };
 
 export { SyncApi };
diff --git a/packages/web/src/common/store/helpers/index.ts b/packages/web/src/common/store/helpers/index.ts
index 048ba767..73942dd3 100644
--- a/packages/web/src/common/store/helpers/index.ts
+++ b/packages/web/src/common/store/helpers/index.ts
@@ -46,18 +46,21 @@ export const createAsyncSlice = <
   RequestPayload,
   SuccessPayload = undefined,
   ErrorPayload = undefined,
+  ExtraState = never,
 >(
-  options: _CreateSliceOptions<AsyncState<SuccessPayload, ErrorPayload>>,
+  options: _CreateSliceOptions<
+    AsyncState<SuccessPayload, ErrorPayload> & ExtraState
+  >,
 ) => {
-  type StateType = _AsyncState<SuccessPayload, ErrorPayload>;
+  type StateType = Exclude<typeof options.initialState, undefined>;
 
-  const initialState: StateType = {
+  const initialState = {
     isProcessing: false,
     reason: null,
     isSuccess: false,
     error: null,
     value: null,
-  };
+  } as unknown as StateType;
 
   const setContext = (
     state: StateType,
@@ -135,9 +138,7 @@ export const createAsyncSlice = <
       name: `async/${options.name}`,
       // TS has bad time figuring out the dynamic nature of the reducers object
       // so need to assert it.
-      reducers: reducers as SliceCaseReducers<
-        _AsyncState<SuccessPayload, ErrorPayload>
-      >,
+      reducers: reducers as SliceCaseReducers<StateType>,
     }),
     actionNames,
   };
diff --git a/packages/web/src/common/styles/animations/rotate.ts b/packages/web/src/common/styles/animations/rotate.ts
new file mode 100644
index 00000000..460c2350
--- /dev/null
+++ b/packages/web/src/common/styles/animations/rotate.ts
@@ -0,0 +1,28 @@
+import { css, keyframes } from "styled-components";
+
+export const rotate = keyframes`
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+`;
+
+export const rotateAnimation = <
+  T extends {
+    duration?: string;
+    loop?: number | "infinite" | "initial" | "inherit";
+    paused?: boolean;
+  },
+>({
+  duration,
+  loop,
+  paused,
+}: T) => {
+  const pause = paused ? "paused" : "running";
+
+  return css<T>`
+    ${rotate} ${duration ?? "1.1s"} linear ${loop ?? "infinite"} ${pause}
+  `;
+};
diff --git a/packages/web/src/components/DatePicker/styled.ts b/packages/web/src/components/DatePicker/styled.ts
index b858651c..44aa3b31 100644
--- a/packages/web/src/components/DatePicker/styled.ts
+++ b/packages/web/src/components/DatePicker/styled.ts
@@ -117,15 +117,20 @@ export const StyledDatePicker = styled.div.attrs<Props>((props) => ({
 
       &--selected {
         background-color: ${({ selectedColor }) => selectedColor};
+        color: white;
       }
 
-      &--today {
+      &--today:not(.react-datepicker__day--selected) {
         color: ${({ view }) =>
           view === "sidebar" ? theme.color.text.accent : theme.color.text.dark};
         text-decoration: underline;
         text-decoration-color: ${({ view }) =>
           view === "sidebar" ? theme.color.text.accent : theme.color.text.dark};
         text-underline-offset: 3px;
+
+        &:hover {
+          color: ${({ theme }) => theme.color.text.dark};
+        }
       }
 
       &--keyboard-selected {
diff --git a/packages/web/src/components/Icons/Spinner.tsx b/packages/web/src/components/Icons/Spinner.tsx
new file mode 100644
index 00000000..9b23c871
--- /dev/null
+++ b/packages/web/src/components/Icons/Spinner.tsx
@@ -0,0 +1,9 @@
+import styled from "styled-components";
+import { SpinnerGap } from "@phosphor-icons/react";
+import { rotateAnimation } from "@web/common/styles/animations/rotate";
+import { iconStyles } from "./styled";
+
+export const SpinnerIcon = styled(SpinnerGap)`
+  ${iconStyles}
+  animation: ${rotateAnimation};
+`;
diff --git a/packages/web/src/components/Tooltip/TooltipWrapper.tsx b/packages/web/src/components/Tooltip/TooltipWrapper.tsx
index b111521d..b375a190 100644
--- a/packages/web/src/components/Tooltip/TooltipWrapper.tsx
+++ b/packages/web/src/components/Tooltip/TooltipWrapper.tsx
@@ -13,14 +13,14 @@ import { StyledShortcutTip } from "./styled";
 export interface Props {
   children: ReactNode;
   description?: string;
-  onClick: () => void;
+  onClick?: () => void;
   shortcut?: string | ReactNode;
 }
 
 export const TooltipWrapper: React.FC<Props> = ({
   children,
   description,
-  onClick,
+  onClick = () => {},
   shortcut,
 }) => {
   const [isTooltipOpen, setIsTooltipOpen] = useState(false);
diff --git a/packages/web/src/ducks/events/sagas/sync.sagas.ts b/packages/web/src/ducks/events/sagas/sync.sagas.ts
new file mode 100644
index 00000000..b11f2b0b
--- /dev/null
+++ b/packages/web/src/ducks/events/sagas/sync.sagas.ts
@@ -0,0 +1,15 @@
+import { call, put } from "@redux-saga/core/effects";
+import { handleError } from "@web/common/utils/event.util";
+import { SyncApi } from "../../../common/apis/sync.api";
+import { importGCalSlice } from "../slices/sync.slice";
+
+export function* importGCal() {
+  try {
+    yield call(SyncApi.importGCal);
+
+    yield put(importGCalSlice.actions.success(undefined));
+  } catch (error) {
+    yield put(importGCalSlice.actions.error(undefined));
+    handleError(error as Error);
+  }
+}
diff --git a/packages/web/src/ducks/events/selectors/sync.selector.ts b/packages/web/src/ducks/events/selectors/sync.selector.ts
index 9b9d9e93..8c0c820f 100644
--- a/packages/web/src/ducks/events/selectors/sync.selector.ts
+++ b/packages/web/src/ducks/events/selectors/sync.selector.ts
@@ -1,3 +1,6 @@
 import { RootState } from "@web/store";
 
-export const selectSyncState = (state: RootState) => state.sync;
+export const selectImportLatestState = ({ sync }: RootState) =>
+  sync.importLatest;
+
+export const selectImportGCalState = ({ sync }: RootState) => sync.importGCal;
diff --git a/packages/web/src/ducks/events/slices/sync.slice.ts b/packages/web/src/ducks/events/slices/sync.slice.ts
index c72294c3..2d62564e 100644
--- a/packages/web/src/ducks/events/slices/sync.slice.ts
+++ b/packages/web/src/ducks/events/slices/sync.slice.ts
@@ -1,5 +1,6 @@
 import { PayloadAction, createSlice } from "@reduxjs/toolkit";
 import { Sync_AsyncStateContextReason } from "@web/ducks/events/context/sync.context";
+import { createAsyncSlice } from "../../../common/store/helpers";
 
 type Payload_TriggerFetch = {
   reason?: Sync_AsyncStateContextReason;
@@ -15,8 +16,23 @@ const initialState: State_Sync = {
   reason: null,
 };
 
-export const syncSlice = createSlice({
-  name: "sync",
+export const importGCalSlice = createAsyncSlice<
+  never,
+  never,
+  never,
+  { importing: boolean }
+>({
+  name: "importGCal",
+  initialState: { importing: false },
+  reducers: {
+    importing: (state, action: PayloadAction<boolean>) => {
+      state.importing = action.payload;
+    },
+  },
+});
+
+export const importLatestSlice = createSlice({
+  name: "importLatest",
   initialState,
   reducers: {
     triggerFetch: {
@@ -47,4 +63,4 @@ export const syncSlice = createSlice({
   },
 });
 
-export const { triggerFetch, resetIsFetchNeeded } = syncSlice.actions;
+export const { triggerFetch, resetIsFetchNeeded } = importLatestSlice.actions;
diff --git a/packages/web/src/socket/SocketProvider.tsx b/packages/web/src/socket/SocketProvider.tsx
index 6efa8154..81a8e49e 100644
--- a/packages/web/src/socket/SocketProvider.tsx
+++ b/packages/web/src/socket/SocketProvider.tsx
@@ -1,12 +1,19 @@
 import { ReactNode } from "react";
 import { useDispatch } from "react-redux";
 import { Socket, io } from "socket.io-client";
-import { EVENT_CHANGED } from "@core/constants/websocket.constants";
+import {
+  EVENT_CHANGED,
+  IMPORT_GCAL_END,
+  IMPORT_GCAL_START,
+} from "@core/constants/websocket.constants";
 import { ServerToClientEvents } from "@core/types/websocket.types";
 import { useUser } from "@web/auth/UserContext";
 import { ENV_WEB } from "@web/common/constants/env.constants";
 import { Sync_AsyncStateContextReason } from "@web/ducks/events/context/sync.context";
-import { triggerFetch } from "@web/ducks/events/slices/sync.slice";
+import {
+  importGCalSlice,
+  triggerFetch,
+} from "@web/ducks/events/slices/sync.slice";
 
 const SocketProvider = ({ children }: { children: ReactNode }) => {
   const dispatch = useDispatch();
@@ -35,6 +42,15 @@ const SocketProvider = ({ children }: { children: ReactNode }) => {
     );
   });
 
+  socket.on(IMPORT_GCAL_START, () => {
+    dispatch(importGCalSlice.actions.request(undefined));
+    dispatch(importGCalSlice.actions.importing(true));
+  });
+
+  socket.on(IMPORT_GCAL_END, () => {
+    dispatch(importGCalSlice.actions.importing(false));
+  });
+
   return children;
 };
 
diff --git a/packages/web/src/store/reducers.ts b/packages/web/src/store/reducers.ts
index b98ed102..ff0f0997 100644
--- a/packages/web/src/store/reducers.ts
+++ b/packages/web/src/store/reducers.ts
@@ -8,7 +8,10 @@ import {
   getCurrentMonthEventsSlice,
 } from "@web/ducks/events/slices/event.slice";
 import { getSomedayEventsSlice } from "@web/ducks/events/slices/someday.slice";
-import { syncSlice } from "@web/ducks/events/slices/sync.slice";
+import {
+  importGCalSlice,
+  importLatestSlice,
+} from "@web/ducks/events/slices/sync.slice";
 import { viewSlice } from "@web/ducks/events/slices/view.slice";
 import { getWeekEventsSlice } from "@web/ducks/events/slices/week.slice";
 import { settingsSlice } from "@web/ducks/settings/slices/settings.slice";
@@ -24,9 +27,14 @@ const eventsReducer = combineReducers({
   getWeekEvents: getWeekEventsSlice.reducer,
 });
 
+const syncReducer = combineReducers({
+  importGCal: importGCalSlice.reducer,
+  importLatest: importLatestSlice.reducer,
+});
+
 export const reducers = {
   events: eventsReducer,
   settings: settingsSlice.reducer,
-  sync: syncSlice.reducer,
+  sync: syncReducer,
   view: viewSlice.reducer,
 };
diff --git a/packages/web/src/store/sagas.ts b/packages/web/src/store/sagas.ts
index daf64d06..c18b81f9 100644
--- a/packages/web/src/store/sagas.ts
+++ b/packages/web/src/store/sagas.ts
@@ -13,6 +13,7 @@ import {
   getSomedayEvents,
   reorderSomedayEvents,
 } from "@web/ducks/events/sagas/someday.sagas";
+import { importGCal } from "@web/ducks/events/sagas/sync.sagas";
 import {
   createEventSlice,
   deleteEventSlice,
@@ -20,10 +21,11 @@ import {
   getCurrentMonthEventsSlice,
 } from "@web/ducks/events/slices/event.slice";
 import { getSomedayEventsSlice } from "@web/ducks/events/slices/someday.slice";
+import { importGCalSlice } from "@web/ducks/events/slices/sync.slice";
 import { getWeekEventsSlice } from "@web/ducks/events/slices/week.slice";
 
 export function* sagas() {
-  yield all([eventSagas(), somedayEventSagas()]);
+  yield all([eventSagas(), somedayEventSagas(), syncSagas()]);
 }
 
 function* eventSagas() {
@@ -44,3 +46,7 @@ function* somedayEventSagas() {
   yield takeLatest(getSomedayEventsSlice.actions.delete, deleteSomedayEvent);
   yield takeLatest(getSomedayEventsSlice.actions.reorder, reorderSomedayEvents);
 }
+
+function* syncSagas() {
+  yield takeLatest(importGCalSlice.actions.request, importGCal);
+}
diff --git a/packages/web/src/views/Calendar/components/Sidebar/SidebarIconRow/SidebarIconRow.tsx b/packages/web/src/views/Calendar/components/Sidebar/SidebarIconRow/SidebarIconRow.tsx
index 2bfb74f9..58949505 100644
--- a/packages/web/src/views/Calendar/components/Sidebar/SidebarIconRow/SidebarIconRow.tsx
+++ b/packages/web/src/views/Calendar/components/Sidebar/SidebarIconRow/SidebarIconRow.tsx
@@ -3,9 +3,11 @@ import { theme } from "@web/common/styles/theme";
 import { getMetaKey } from "@web/common/utils/shortcut.util";
 import { CalendarIcon } from "@web/components/Icons/Calendar";
 import { CommandIcon } from "@web/components/Icons/Command";
+import { SpinnerIcon } from "@web/components/Icons/Spinner";
 import { TodoIcon } from "@web/components/Icons/Todo";
 import { Text } from "@web/components/Text";
 import { TooltipWrapper } from "@web/components/Tooltip/TooltipWrapper";
+import { selectImportGCalState } from "@web/ducks/events/selectors/sync.selector";
 import { selectSidebarTab } from "@web/ducks/events/selectors/view.selectors";
 import { viewSlice } from "@web/ducks/events/slices/view.slice";
 import { selectIsCmdPaletteOpen } from "@web/ducks/settings/selectors/settings.selectors";
@@ -16,6 +18,7 @@ import { IconRow, LeftIconGroup } from "../styled";
 export const SidebarIconRow = () => {
   const dispatch = useAppDispatch();
   const tab = useAppSelector(selectSidebarTab);
+  const gCalImport = useAppSelector(selectImportGCalState);
 
   const isCmdPaletteOpen = useAppSelector(selectIsCmdPaletteOpen);
 
@@ -79,6 +82,11 @@ export const SidebarIconRow = () => {
             }
           />
         </TooltipWrapper>
+        {gCalImport.importing ? (
+          <TooltipWrapper description="Importing your calendar events in the background">
+            <SpinnerIcon disabled />
+          </TooltipWrapper>
+        ) : undefined}
       </LeftIconGroup>
     </IconRow>
   );
diff --git a/packages/web/src/views/Calendar/hooks/useRefresh.ts b/packages/web/src/views/Calendar/hooks/useRefresh.ts
index 717e6803..2a3b4656 100644
--- a/packages/web/src/views/Calendar/hooks/useRefresh.ts
+++ b/packages/web/src/views/Calendar/hooks/useRefresh.ts
@@ -2,7 +2,7 @@ import { useEffect } from "react";
 import { toUTCOffset } from "@web/common/utils/web.date.util";
 import { Sync_AsyncStateContextReason } from "@web/ducks/events/context/sync.context";
 import { Week_AsyncStateContextReason } from "@web/ducks/events/context/week.context";
-import { selectSyncState } from "@web/ducks/events/selectors/sync.selector";
+import { selectImportLatestState } from "@web/ducks/events/selectors/sync.selector";
 import { selectDatesInView } from "@web/ducks/events/selectors/view.selectors";
 import { resetIsFetchNeeded } from "@web/ducks/events/slices/sync.slice";
 import { getWeekEventsSlice } from "@web/ducks/events/slices/week.slice";
@@ -10,7 +10,7 @@ import { useAppDispatch, useAppSelector } from "@web/store/store.hooks";
 
 export const useRefresh = () => {
   const dispatch = useAppDispatch();
-  const { isFetchNeeded, reason } = useAppSelector(selectSyncState);
+  const { isFetchNeeded, reason } = useAppSelector(selectImportLatestState);
   const { start, end } = useAppSelector(selectDatesInView);
 
   useEffect(() => {


Test patch:
diff --git a/packages/backend/src/sync/controllers/sync.controller.test.ts b/packages/backend/src/sync/controllers/sync.controller.test.ts
index 74b00665..ab313944 100644
--- a/packages/backend/src/sync/controllers/sync.controller.test.ts
+++ b/packages/backend/src/sync/controllers/sync.controller.test.ts
@@ -1,4 +1,7 @@
-import { EVENT_CHANGED } from "@core/constants/websocket.constants";
+import {
+  EVENT_CHANGED,
+  IMPORT_GCAL_END,
+} from "@core/constants/websocket.constants";
 import { isBase, isExistingInstance } from "@core/util/event/event.util";
 import { BaseDriver } from "@backend/__tests__/drivers/base.driver";
 import { SyncControllerDriver } from "@backend/__tests__/drivers/sync.controller.driver";
@@ -141,8 +144,12 @@ describe("SyncController", () => {
       expect(regularEvents[0]?.gEventId).toBe("regular-1");
     });
 
+    it("should notify the frontend that the import is complete", async () => {
+      expect(notifySpy).toHaveBeenNthCalledWith(1, IMPORT_GCAL_END, socketId);
+    });
+
     it("should notify the frontend to refetch the calendar events on completion", async () => {
-      expect(notifySpy).toHaveBeenNthCalledWith(1, EVENT_CHANGED, socketId);
+      expect(notifySpy).toHaveBeenLastCalledWith(EVENT_CHANGED, socketId);
     });
   });
 });
diff --git a/packages/web/src/__tests__/Calendar/calendar.render.test.utils.ts b/packages/web/src/__tests__/Calendar/calendar.render.test.utils.ts
index d8cd0945..36601335 100644
--- a/packages/web/src/__tests__/Calendar/calendar.render.test.utils.ts
+++ b/packages/web/src/__tests__/Calendar/calendar.render.test.utils.ts
@@ -115,13 +115,20 @@ export const createInitialState = (
         tab: "tasks",
         isOpen: true,
       },
+      header: {
+        note: {
+          focus: false,
+        },
+      },
     },
     settings: {
       isCmdPaletteOpen: false,
     },
     sync: {
-      isFetchNeeded: false,
-      reason: null,
+      importLatest: {
+        isFetchNeeded: false,
+        reason: null,
+      },
     },
     ...partialState,
   };
diff --git a/packages/web/src/__tests__/__mocks__/state/state.weekEvents.ts b/packages/web/src/__tests__/__mocks__/state/state.weekEvents.ts
index 1eaca767..87b9d238 100644
--- a/packages/web/src/__tests__/__mocks__/state/state.weekEvents.ts
+++ b/packages/web/src/__tests__/__mocks__/state/state.weekEvents.ts
@@ -56,8 +56,10 @@ export const preloadedState: InitialReduxState = {
     isCmdPaletteOpen: false,
   },
   sync: {
-    isFetchNeeded: false,
-    reason: null,
+    importLatest: {
+      isFetchNeeded: false,
+      reason: null,
+    },
   },
   view: {
     dates: {