Skip to content
6 changes: 6 additions & 0 deletions app/client/src/actions/windowActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";

export const updateWindowDimensions = (height: number, width: number) => ({
type: ReduxActionTypes.UPDATE_WINDOW_DIMENSIONS,
payload: { height, width },
});
5 changes: 5 additions & 0 deletions app/client/src/ce/constants/ReduxActionConstants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,10 @@ const AppThemeActionsTypes = {
RESET_APP_THEME_SUCCESS: "RESET_APP_THEME_SUCCESS",
};

const WindowActionsTypes = {
UPDATE_WINDOW_DIMENSIONS: "UPDATE_WINDOW_DIMENSIONS",
};

const AppThemeActionErrorTypes = {
FETCH_APP_THEMES_ERROR: "FETCH_APP_THEMES_ERROR",
SET_DEFAULT_SELECTED_THEME_ERROR: "SET_DEFAULT_SELECTED_THEME_ERROR",
Expand Down Expand Up @@ -1304,6 +1308,7 @@ export const ReduxActionTypes = {
...AIActionTypes,
...ApplicationActionTypes,
...AppThemeActionsTypes,
...WindowActionsTypes,
...AppViewActionTypes,
...AppSettingsActionTypes,
...BatchUpdateActionTypes,
Expand Down
2 changes: 2 additions & 0 deletions app/client/src/ce/reducers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import type { CrudInfoModalReduxState } from "reducers/uiReducers/crudInfoModalR
import type { FormEvaluationState } from "reducers/evaluationReducers/formEvaluationReducer";
import type { widgetReflow } from "reducers/uiReducers/reflowReducer";
import type { AppThemingState } from "reducers/uiReducers/appThemingReducer";
import type { WindowDimensionsState } from "reducers/uiReducers/windowReducer";
import type { MainCanvasReduxState } from "ee/reducers/uiReducers/mainCanvasReducer";
import type { SettingsReduxState } from "ee/reducers/settingsReducer";
import SettingsReducer from "ee/reducers/settingsReducer";
Expand Down Expand Up @@ -147,6 +148,7 @@ export interface AppState {
activeField: ActiveField;
ide: IDEState;
pluginActionEditor: PluginActionEditorState;
windowDimensions: WindowDimensionsState;
};
entities: {
canvasWidgetsStructure: CanvasWidgetStructure;
Expand Down
2 changes: 2 additions & 0 deletions app/client/src/ce/reducers/uiReducers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import crudInfoModalReducer from "reducers/uiReducers/crudInfoModalReducer";
import { widgetReflowReducer } from "reducers/uiReducers/reflowReducer";
import jsObjectNameReducer from "reducers/uiReducers/jsObjectNameReducer";
import appThemingReducer from "reducers/uiReducers/appThemingReducer";
import windowReducer from "reducers/uiReducers/windowReducer";
import mainCanvasReducer from "ee/reducers/uiReducers/mainCanvasReducer";
import focusHistoryReducer from "reducers/uiReducers/focusHistoryReducer";
import { editorContextReducer } from "ee/reducers/uiReducers/editorContextReducer";
Expand Down Expand Up @@ -85,6 +86,7 @@ export const uiReducerObject = {
crudInfoModal: crudInfoModalReducer,
widgetReflow: widgetReflowReducer,
appTheming: appThemingReducer,
windowDimensions: windowReducer,
mainCanvas: mainCanvasReducer,
appSettingsPane: appSettingsPaneReducer,
focusHistory: focusHistoryReducer,
Expand Down
2 changes: 2 additions & 0 deletions app/client/src/ce/sagas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { watchActionSagas } from "sagas/ActionSagas";
import apiPaneSagas from "sagas/ApiPaneSagas";
import applicationSagas from "ee/sagas/ApplicationSagas";
import appThemingSaga from "sagas/AppThemingSaga";
import windowSaga from "sagas/WindowSaga";
import AutoHeightSagas from "sagas/autoHeightSagas";
import autoLayoutUpdateSagas from "sagas/AutoLayoutUpdateSagas";
import batchSagas from "sagas/BatchSagas";
Expand Down Expand Up @@ -95,6 +96,7 @@ export const sagas = [
gitSyncSagas,
SuperUserSagas,
appThemingSaga,
windowSaga,
NavigationSagas,
editorContextSagas,
AutoHeightSagas,
Expand Down
32 changes: 32 additions & 0 deletions app/client/src/pages/AppIDE/AppIDE.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ import type { Page } from "entities/Page";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
import { GitApplicationContextProvider } from "git-artifact-helpers/application/components";
import { AppIDEModals } from "ee/pages/AppIDE/components/AppIDEModals";
import { updateWindowDimensions } from "actions/windowActions";
import { debounce } from "lodash";
import { RESIZE_DEBOUNCE_THRESHOLD } from "pages/hooks/constants";

interface EditorProps {
currentApplicationId?: string;
Expand All @@ -60,12 +63,15 @@ interface EditorProps {
isMultiPane: boolean;
widgetConfigBuildSuccess: () => void;
pages: Page[];
updateWindowDimensions: (height: number, width: number) => void;
}

type Props = EditorProps & RouteComponentProps<BuilderRouteParams>;

class Editor extends Component<Props> {
prevPageId: string | null = null;
private handleResize: (() => void) | null = null;
private debouncedHandleResize: ReturnType<typeof debounce> | null = null;

componentDidMount() {
const { basePageId } = this.props.match.params || {};
Expand All @@ -75,6 +81,23 @@ class Editor extends Component<Props> {
editorInitializer().then(() => {
this.props.widgetConfigBuildSuccess();
});

// Set up window resize listener for window dimensions
this.handleResize = () => {
this.props.updateWindowDimensions(window.innerHeight, window.innerWidth);
};

// Create debounced version of resize handler
this.debouncedHandleResize = debounce(
this.handleResize,
RESIZE_DEBOUNCE_THRESHOLD * 2,
);

// Set initial dimensions immediately
this.props.updateWindowDimensions(window.innerHeight, window.innerWidth);

// Add resize listener with debounced handler
window.addEventListener("resize", this.debouncedHandleResize);
}

shouldComponentUpdate(nextProps: Props) {
Expand Down Expand Up @@ -159,6 +182,13 @@ class Editor extends Component<Props> {
componentWillUnmount() {
this.props.resetEditorRequest();
urlBuilder.setCurrentBasePageId(null);

// Clean up window resize listener
if (this.debouncedHandleResize) {
window.removeEventListener("resize", this.debouncedHandleResize);
// Cancel any pending debounced calls
this.debouncedHandleResize.cancel();
}
}

public render() {
Expand Down Expand Up @@ -218,6 +248,8 @@ const mapDispatchToProps = (dispatch: any) => {
setupPage: (pageId: string) => dispatch(setupPageAction({ id: pageId })),
updateCurrentPage: (pageId: string) => dispatch(updateCurrentPage(pageId)),
widgetConfigBuildSuccess: () => dispatch(widgetInitialisationSuccess()),
updateWindowDimensions: (height: number, width: number) =>
dispatch(updateWindowDimensions(height, width)),
};
};

Expand Down
32 changes: 31 additions & 1 deletion app/client/src/pages/AppViewer/AppPage/AppPage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React, { useEffect, useMemo, useRef } from "react";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import type { CanvasWidgetStructure } from "WidgetProvider/types";
import { useSelector } from "react-redux";
import { useSelector, useDispatch } from "react-redux";
import { getAppMode } from "ee/selectors/applicationSelectors";
import { APP_MODE } from "entities/App";
import { renderAppsmithCanvas } from "layoutSystems/CanvasFactory";
import type { WidgetProps } from "widgets/BaseWidget";
import { useAppViewerSidebarProperties } from "utils/hooks/useAppViewerSidebarProperties";
import { getIsAnvilLayout } from "layoutSystems/anvil/integrations/selectors";
import { updateWindowDimensions } from "actions/windowActions";
import { debounce } from "lodash";
import { RESIZE_DEBOUNCE_THRESHOLD } from "pages/hooks/constants";

import { PageView, PageViewWrapper } from "./AppPage.styled";
import { useCanvasWidthAutoResize } from "../../hooks/useCanvasWidthAutoResize";
Expand All @@ -24,6 +27,7 @@ export function AppPage(props: AppPageProps) {
const { appName, basePageId, canvasWidth, pageName, widgetsStructure } =
props;

const dispatch = useDispatch();
const appMode = useSelector(getAppMode);
const isPublished = appMode === APP_MODE.PUBLISHED;
const isAnvilLayout = useSelector(getIsAnvilLayout);
Expand All @@ -46,6 +50,32 @@ export function AppPage(props: AppPageProps) {
});
}, [appName, basePageId, pageName]);

// Set up window resize listener for window dimensions
useEffect(() => {
const handleResize = () => {
dispatch(updateWindowDimensions(window.innerHeight, window.innerWidth));
};

// Create debounced version of resize handler
const debouncedHandleResize = debounce(
handleResize,
RESIZE_DEBOUNCE_THRESHOLD * 2,
);

// Set initial dimensions immediately
dispatch(updateWindowDimensions(window.innerHeight, window.innerWidth));

// Add resize listener with debounced handler
window.addEventListener("resize", debouncedHandleResize);

// Cleanup
return () => {
window.removeEventListener("resize", debouncedHandleResize);
// Cancel any pending debounced calls
debouncedHandleResize.cancel();
};
}, [dispatch]);

return (
<PageViewWrapper
hasPinnedSidebar={hasSidebarPinned}
Expand Down
25 changes: 25 additions & 0 deletions app/client/src/reducers/uiReducers/windowReducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createImmerReducer } from "utils/ReducerUtils";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import type { ReduxAction } from "actions/ReduxActionTypes";

export interface WindowDimensionsState {
height: number;
width: number;
}

const initialState: WindowDimensionsState = {
height: typeof window !== "undefined" ? window.innerHeight : 0,
width: typeof window !== "undefined" ? window.innerWidth : 0,
};

const windowReducer = createImmerReducer(initialState, {
[ReduxActionTypes.UPDATE_WINDOW_DIMENSIONS]: (
state: WindowDimensionsState,
action: ReduxAction<{ height: number; width: number }>,
) => {
state.height = action.payload.height;
state.width = action.payload.width;
},
});

export default windowReducer;
18 changes: 18 additions & 0 deletions app/client/src/sagas/WindowSaga.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import { takeLatest, select, call } from "redux-saga/effects";
import { evaluateTreeSaga } from "./EvaluationsSaga";
import { getUnevaluatedDataTree } from "selectors/dataTreeSelectors";

export function* handleWindowDimensionsUpdate() {
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
yield select(getUnevaluatedDataTree);

yield call(evaluateTreeSaga, unEvalAndConfigTree);
}

export default function* windowSaga() {
yield takeLatest(
ReduxActionTypes.UPDATE_WINDOW_DIMENSIONS,
handleWindowDimensionsUpdate,
);
}
6 changes: 5 additions & 1 deletion app/client/src/selectors/dataTreeSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
import "url-search-params-polyfill";
import type { DefaultRootState } from "react-redux";
import { getSelectedAppThemeProperties } from "./appThemingSelectors";
import { getWindowDimensions } from "./windowSelectors";
import type { LoadingEntitiesState } from "reducers/evaluationReducers/loadingEntitiesReducer";
import _, { get } from "lodash";
import type { EvaluationError } from "utils/DynamicBindingUtils";
Expand Down Expand Up @@ -149,6 +150,7 @@ export const getUnevaluatedDataTree = createSelector(
getMetaWidgetsFromUnevaluatedDataTree,
getAppData,
getSelectedAppThemeProperties,
getWindowDimensions,
getCurrentAppWorkspace,
getCurrentApplication,
getCurrentPageName,
Expand All @@ -160,6 +162,7 @@ export const getUnevaluatedDataTree = createSelector(
metaWidgets,
appData,
theme,
windowDimensions,
currentWorkspace,
currentApplication,
getCurrentPageName,
Expand All @@ -182,12 +185,13 @@ export const getUnevaluatedDataTree = createSelector(
// taking precedence in case the key is the same
store: appData.store,
theme,
ui: windowDimensions,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the structure of appsmith.ui to match the intended API.

The assignment ui: windowDimensions directly exposes the window dimensions object (with height and width properties) as appsmith.ui. Ensure this matches the desired public API structure and doesn't conflict with any existing ui property.


🏁 Script executed:

#!/bin/bash
# Search for existing references to appsmith.ui to verify structure expectations
rg -nP 'appsmith\.ui\b' --type=ts --type=tsx -C3

Length of output: 27



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for existing references to appsmith.ui with corrected type flags
rg -nP 'appsmith\.ui\b' --type=typescript -C3

Length of output: 34


🏁 Script executed:

#!/bin/bash
# Also search for ui property assignments and windowDimensions usage in dataTree context
rg -nP '(ui\s*:|windowDimensions)' app/client/src/selectors/dataTreeSelectors.ts -C5

Length of output: 691


🏁 Script executed:

#!/bin/bash
# Search for dataTree object construction to understand the structure
rg -nP 'dataTree\s*=\s*\{' --type=typescript -C10

Length of output: 34



---

🏁 Script executed:

```shell
#!/bin/bash
# Find the AppsmithEntity type definition
rg -nP 'type AppsmithEntity|interface AppsmithEntity' -C5

Length of output: 2717


🏁 Script executed:

#!/bin/bash
# Search for ui property references in the same file
rg -n 'ui:' app/client/src/selectors/dataTreeSelectors.ts

Length of output: 31


🏁 Script executed:

#!/bin/bash
# Find windowDimensions type/definition
rg -nP 'windowDimensions\s*[=:|]' -C3 | head -50

Length of output: 613


🏁 Script executed:

#!/bin/bash
# Search for appsmith.ui references without type flags
rg -n 'appsmith\.ui' -C2

Length of output: 3987



---

🏁 Script executed:

```shell
#!/bin/bash
# Get the complete AppsmithEntity interface definition
rg -n 'export interface AppsmithEntity' -A20 app/client/src/ce/entities/DataTree/types.ts

Length of output: 451


🏁 Script executed:

#!/bin/bash
# Also check if ui is defined in AppDataState (which AppsmithEntity extends from)
rg -n 'interface AppDataState|type AppDataState' -A15

Length of output: 1319



---

🏁 Script executed:

```shell
#!/bin/bash
# Check the complete selector function and type casting context around line 193
rg -n 'as AppsmithEntity' app/client/src/selectors/dataTreeSelectors.ts -B10 -A2

Length of output: 656


🏁 Script executed:

#!/bin/bash
# Search for any type extensions or augmentations of AppsmithEntity that add ui property
rg -n 'AppsmithEntity' app/client/src/ce/entities/DataTree/types.ts -A30

Length of output: 906


Add ui property to the AppsmithEntity interface definition.

The assignment of ui: windowDimensions at line 188 is being bypassed by the as AppsmithEntity cast at line 193, but the ui property is not declared in the interface. Production code in ExternalWidget and CustomWidget actively consumes appsmith.ui with a {width, height} structure, confirming this is intentional.

Update app/client/src/ce/entities/DataTree/types.ts to declare the property:

export interface AppsmithEntity extends Omit<AppDataState, "store"> {
  ENTITY_TYPE: typeof ENTITY_TYPE.APPSMITH;
  store: Record<string, unknown>;
  theme: AppTheme["properties"];
  ui: { width: number; height: number };
  currentPageName: string;
  workspaceName: string;
  appName: string;
  currentEnvironmentName: string;
}
🤖 Prompt for AI Agents
In app/client/src/ce/entities/DataTree/types.ts (update the AppsmithEntity
interface), add the missing ui property declaration so AppsmithEntity includes
ui: { width: number; height: number }; keep the rest of the interface as-is
(Omit<AppDataState, "store">, ENTITY_TYPE, store, theme, currentPageName,
workspaceName, appName, currentEnvironmentName) to match runtime usage where
code sets appsmith.ui = windowDimensions; ensure any necessary imports/types
referenced (e.g., AppTheme) remain unchanged.

currentPageName: getCurrentPageName,
workspaceName: currentWorkspace.name,
appName: currentApplication?.name,
currentEnvironmentName,
ENTITY_TYPE: ENTITY_TYPE.APPSMITH,
} as AppsmithEntity;
(dataTree.appsmith as AppsmithEntity).ENTITY_TYPE = ENTITY_TYPE.APPSMITH;
dataTree = { ...dataTree, ...metaWidgets.dataTree };
configTree = { ...configTree, ...metaWidgets.configTree };

Expand Down
5 changes: 5 additions & 0 deletions app/client/src/selectors/windowSelectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { DefaultRootState } from "react-redux";

export const getWindowDimensions = (state: DefaultRootState) => {
return state.ui.windowDimensions;
};
Loading