Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/client/src/ce/IDE/Interfaces/IDETypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const IDE_TYPE = {
None: "None",
App: "App",
UIPackage: "UIPackage",
Workspace: "Workspace",
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

LGTM — new IDE type addition is consistent.
No concerns here. Please ensure downstream switch/route maps cover IDE_TYPE.Workspace.

Run to spot any missing cases:


🏁 Script executed:

#!/bin/bash
rg -nP --type=ts '\bIDE_TYPE\.Workspace\b|:\s*IDEType\b' -C2

Length of output: 8283



---

🏁 Script executed:

```shell
#!/bin/bash
# Find switch statements handling IDE_TYPE
rg -nP --type=ts 'switch\s*\(\s*(type|ideType|.*IDE_TYPE)' -A 15 | head -100

Length of output: 8750


🏁 Script executed:

#!/bin/bash
# Find other enum-like mappings that use IDE_TYPE keys
rg -nP --type=ts '\[IDE_TYPE\.' -C 1 | head -150

Length of output: 498


🏁 Script executed:

#!/bin/bash
# Check if there are any references to IDE_TYPE.Workspace that might be missing
rg -nP --type=ts 'IDE_TYPE\.Workspace' -C 3

Length of output: 386


🏁 Script executed:

#!/bin/bash
# Get full getIDEFocusStrategy function to check if Workspace case is handled
cat app/client/src/ce/navigation/FocusStrategy/index.ts

Length of output: 528


🏁 Script executed:

#!/bin/bash
# Search for getIDETypeByUrl to see if it handles Workspace
cat app/client/src/ce/entities/IDE/utils/getIDETypeByUrl.ts

Length of output: 434


🏁 Script executed:

#!/bin/bash
# Search for EE FocusStrategy implementations
fd -t f 'FocusStrategy' -path '*/ee/*'

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Check if UIPackageFocusStrategy or WorkspaceFocusStrategy exists
rg -nP --type=ts 'UIPackageFocusStrategy|WorkspaceFocusStrategy' -l

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find AppIDEFocusStrategy to understand implementation pattern
rg -nP --type=ts 'class AppIDEFocusStrategy|export.*AppIDEFocusStrategy' -C 3

Length of output: 784


🏁 Script executed:

#!/bin/bash
# Search more broadly for strategy files in EE folder
find . -path "*/ee/*" -name "*FocusStrategy*" 2>/dev/null | head -20

Length of output: 179


🏁 Script executed:

#!/bin/bash
# Check if there's an EE version of the focus strategy index
find . -path "*/ee/*" -name "index.ts" | xargs grep -l "getIDEFocusStrategy\|FocusStrategy" 2>/dev/null | head -10

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Check the EE version of getIDEFocusStrategy
cat app/client/src/ee/navigation/FocusStrategy/index.ts

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if getIDEFocusStrategy is called with Workspace anywhere
rg -nP --type=ts 'getIDEFocusStrategy' -C 3

Length of output: 2489


Add case for IDE_TYPE.Workspace in getIDEFocusStrategy or update default handler.

The switch statement in app/client/src/ce/navigation/FocusStrategy/index.ts only handles IDE_TYPE.None and IDE_TYPE.App, but IDE_TYPE.Workspace is now in IDEBasePaths. Since FocusRetentionSaga calls getIDEFocusStrategy(ideType) where ideType comes from getIDETypeByUrl(), this will throw an error if Workspace paths are configured.

Currently, IDE_TYPE.Workspace has an empty path array, so it won't trigger at runtime yet—but this is a latent bug waiting to happen.

🤖 Prompt for AI Agents
In app/client/src/ce/IDE/Interfaces/IDETypes.ts around line 5,
IDE_TYPE.Workspace was added but getIDEFocusStrategy (in
app/client/src/ce/navigation/FocusStrategy/index.ts) only handles IDE_TYPE.None
and IDE_TYPE.App; add a case for IDE_TYPE.Workspace to the switch in
getIDEFocusStrategy (or update the switch default to handle unknown/added IDE
types safely) so FocusRetentionSaga won't throw when getIDETypeByUrl() returns
Workspace; implement the Workspace case to return the appropriate focus strategy
(match App behavior or a no-op strategy) and ensure the default branch logs a
warning and returns a safe fallback strategy.

} as const;

export type IDEType = keyof typeof IDE_TYPE;
1 change: 1 addition & 0 deletions app/client/src/ce/IDE/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ export const IDEBasePaths: Readonly<Record<IDEType, string[]>> = {
[IDE_TYPE.None]: [],
[IDE_TYPE.App]: [BUILDER_PATH, BUILDER_PATH_DEPRECATED, BUILDER_CUSTOM_PATH],
[IDE_TYPE.UIPackage]: [],
[IDE_TYPE.Workspace]: [],
};
8 changes: 8 additions & 0 deletions app/client/src/ce/RouteBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@ export const queryAddURL = (props: URLBuilderParams): string =>
suffix: `queries/add`,
});

export const workspaceDatasourcesURL = (workspaceId: string): string =>
`/workspace/${workspaceId}/datasources`;

export const workspaceDatasourceEditorURL = (
workspaceId: string,
datasourceId: string,
): string => `/workspace/${workspaceId}/datasources/${datasourceId}`;

export const appLibrariesURL = (): string =>
urlBuilder.build({
suffix: "libraries",
Expand Down
10 changes: 10 additions & 0 deletions app/client/src/ce/actions/workspaceIDEActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";

export interface InitWorkspaceIDEPayload {
workspaceId: string;
}

export const initWorkspaceIDE = (payload: InitWorkspaceIDEPayload) => ({
type: ReduxActionTypes.INITIALIZE_WORKSPACE_IDE,
payload,
});
3 changes: 3 additions & 0 deletions app/client/src/ce/constants/ReduxActionConstants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,8 @@ const IDEActionTypes = {
INITIALIZE_CURRENT_PAGE: "INITIALIZE_CURRENT_PAGE",
INITIALIZE_EDITOR: "INITIALIZE_EDITOR",
INITIALIZE_EDITOR_SUCCESS: "INITIALIZE_EDITOR_SUCCESS",
INITIALIZE_WORKSPACE_IDE: "INITIALIZE_WORKSPACE_IDE",
INITIALIZE_WORKSPACE_IDE_SUCCESS: "INITIALIZE_WORKSPACE_IDE_SUCCESS",
INITIALIZE_PAGE_VIEWER: "INITIALIZE_PAGE_VIEWER",
INITIALIZE_PAGE_VIEWER_SUCCESS: "INITIALIZE_PAGE_VIEWER_SUCCESS",
SET_EXPLORER_PINNED: "SET_EXPLORER_PINNED",
Expand Down Expand Up @@ -478,6 +480,7 @@ const IDEActionTypes = {
const IDEActionErrorTypes = {
SETUP_PAGE_ERROR: "SETUP_PAGE_ERROR",
SETUP_PUBLISHED_PAGE_ERROR: "SETUP_PUBLISHED_PAGE_ERROR",
INITIALIZE_WORKSPACE_IDE_ERROR: "INITIALIZE_WORKSPACE_IDE_ERROR",
};

const ErrorManagementActionTypes = {
Expand Down
109 changes: 109 additions & 0 deletions app/client/src/ce/entities/Engine/WorkspaceEditorEngine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { call, put } from "redux-saga/effects";
import CodemirrorTernService from "utils/autocomplete/CodemirrorTernService";
import {
ReduxActionErrorTypes,
ReduxActionTypes,
} from "ee/constants/ReduxActionConstants";
import { isAirgapped } from "ee/utils/airgapHelpers";
import { fetchPluginFormConfigs, fetchPlugins } from "actions/pluginActions";
import {
fetchDatasources,
fetchMockDatasources,
} from "actions/datasourceActions";
import { failFastApiCalls } from "sagas/InitSagas";
import {
PluginFormConfigsNotFoundError,
PluginsNotFoundError,
} from "entities/Engine";
import type { ReduxAction } from "actions/ReduxActionTypes";
import { waitForFetchEnvironments } from "ee/sagas/EnvironmentSagas";
import { fetchingEnvironmentConfigs } from "ee/actions/environmentAction";

export default class WorkspaceEditorEngine {
constructor() {
this.setupEngine = this.setupEngine.bind(this);
this.loadWorkspace = this.loadWorkspace.bind(this);
this.loadPluginsAndDatasources = this.loadPluginsAndDatasources.bind(this);
this.completeChore = this.completeChore.bind(this);
}

*loadWorkspace(workspaceId: string) {
// Set the current workspace context
yield put({
type: ReduxActionTypes.SET_CURRENT_WORKSPACE_ID,
payload: {
workspaceId,
editorId: workspaceId, // Use workspaceId as editorId for workspace context
},
});

// Fetch environment configs for the workspace
yield put(
fetchingEnvironmentConfigs({
workspaceId,
editorId: workspaceId,
fetchDatasourceMeta: true,
}),
);

// Wait for environments to be fetched
yield call(waitForFetchEnvironments);
}

public *setupEngine() {
yield put({ type: ReduxActionTypes.START_EVALUATION });
CodemirrorTernService.resetServer();
}

*loadPluginsAndDatasources(workspaceId: string) {
const isAirgappedInstance = isAirgapped();
const initActions: ReduxAction<unknown>[] = [
fetchPlugins({ workspaceId }),
fetchDatasources({ workspaceId }),
];

const successActions = [
ReduxActionTypes.FETCH_PLUGINS_SUCCESS,
ReduxActionTypes.FETCH_DATASOURCES_SUCCESS,
];

const errorActions = [
ReduxActionErrorTypes.FETCH_PLUGINS_ERROR,
ReduxActionErrorTypes.FETCH_DATASOURCES_ERROR,
];

if (!isAirgappedInstance) {
initActions.push(fetchMockDatasources());
successActions.push(ReduxActionTypes.FETCH_MOCK_DATASOURCES_SUCCESS);
errorActions.push(ReduxActionErrorTypes.FETCH_MOCK_DATASOURCES_ERROR);
}

const initActionCalls: boolean = yield call(
failFastApiCalls,
initActions,
successActions,
errorActions,
);

if (!initActionCalls)
throw new PluginsNotFoundError("Unable to fetch plugins");

const pluginFormCall: boolean = yield call(
failFastApiCalls,
[fetchPluginFormConfigs()],
[ReduxActionTypes.FETCH_PLUGIN_FORM_CONFIGS_SUCCESS],
[ReduxActionErrorTypes.FETCH_PLUGIN_FORM_CONFIGS_ERROR],
);

if (!pluginFormCall)
throw new PluginFormConfigsNotFoundError(
"Unable to fetch plugin form configs",
);
}

*completeChore() {
yield put({
type: ReduxActionTypes.INITIALIZE_WORKSPACE_IDE_SUCCESS,
});
}
}
46 changes: 44 additions & 2 deletions app/client/src/ce/entities/URLRedirect/URLAssembly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { APP_MODE } from "entities/App";
import { generatePath } from "react-router";
import getQueryParamsObject from "utils/getQueryParamsObject";
import { isNil } from "lodash";
import { objectKeys } from "@appsmith/utils";

export interface URLBuilderParams {
suffix?: string;
Expand All @@ -26,6 +27,7 @@ export interface URLBuilderParams {
// This is used to pass ID if the sender doesn't know the type of the entity
// base version of parent entity id, can be basePageId or moduleId
baseParentEntityId?: string;
workspaceId?: string;
generateEditorPath?: boolean;
}

Expand Down Expand Up @@ -65,7 +67,7 @@ export interface PageURLParams {
export function getQueryStringfromObject(
params: Record<string, string> = {},
): string {
const paramKeys = Object.keys(params);
const paramKeys = objectKeys(params);
const queryParams: string[] = [];

if (paramKeys) {
Expand Down Expand Up @@ -241,9 +243,24 @@ export class URLBuilder {
}

generateBasePath(basePageId: string, mode: APP_MODE) {
// Check if we're in workspace context
if (this.isWorkspaceContext()) {
return this.generateBasePathForWorkspace(basePageId);
}

return this.generateBasePathForApp(basePageId, mode);
}

isWorkspaceContext(): boolean {
const currentUrl = window.location.pathname;

return currentUrl.startsWith("/workspace");
}

generateBasePathForWorkspace(workspaceId: string) {
return `/workspace/${workspaceId}/datasources`;
}

getCustomSlugPathPreview(basePageId: string, customSlug: string) {
const urlPattern =
baseURLRegistry[URL_TYPE.CUSTOM_SLUG][APP_MODE.PUBLISHED];
Expand Down Expand Up @@ -284,9 +301,26 @@ export class URLBuilder {
}

resolveEntityId(builderParams: URLBuilderParams): string {
// Check if we're in workspace context
if (this.isWorkspaceContext()) {
return this.resolveEntityIdForWorkspace(builderParams);
}

return this.resolveEntityIdForApp(builderParams);
}

resolveEntityIdForWorkspace(builderParams: URLBuilderParams): string {
// Extract workspaceId from current URL if not provided
if (builderParams?.workspaceId) {
return builderParams.workspaceId;
}

const currentUrl = window.location.pathname;
const workspaceMatch = currentUrl.match(/^\/workspace\/([^\/]+)/);

return workspaceMatch ? workspaceMatch[1] : "";
}

Comment on lines +312 to +323
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 | 🟠 Major

Validate workspaceId; don’t fall back to empty string.

Returning "" yields “/workspace//…” URLs. Align with app path behavior and throw.

-    const workspaceMatch = currentUrl.match(/^\/workspace\/([^\/]+)/);
-
-    return workspaceMatch ? workspaceMatch[1] : "";
+    const workspaceMatch = currentUrl.match(/^\/workspace\/([^\/]+)/);
+    const id = workspaceMatch ? workspaceMatch[1] : "";
+    if (!id) {
+      throw new URIError("Missing workspaceId. Provide builderParams.workspaceId or navigate under /workspace/:workspaceId.");
+    }
+    return id;
🤖 Prompt for AI Agents
In app/client/src/ce/entities/URLRedirect/URLAssembly.ts around lines 312 to
323, the resolver returns an empty string when no workspaceId is found which
produces malformed paths like "/workspace//…"; change the logic to validate and
require a workspaceId: if builderParams.workspaceId is present return it;
otherwise attempt to extract from window.location.pathname and if extraction
fails throw a clear Error (or assert) indicating missing workspaceId instead of
returning ""; ensure the thrown error message includes context (e.g., "Missing
workspaceId in URL or builderParams") so callers can handle or surface it.

/**
* @throws {URIError}
* @param builderParams
Expand All @@ -304,6 +338,14 @@ export class URLBuilder {

const entityId = this.resolveEntityId(builderParams);

// Handle workspace-specific suffix modification
let modifiedSuffix = suffix;

if (this.isWorkspaceContext() && suffix?.startsWith("datasource/")) {
// For workspace context, remove the 'datasource/' prefix from suffix
modifiedSuffix = suffix.replace("datasource/", "");
}

const basePath = this.generateBasePath(entityId, mode);

const queryParamsToPersist = fetchQueryParamsToPersist(
Expand All @@ -320,7 +362,7 @@ export class URLBuilder {

const queryString = getQueryStringfromObject(modifiedQueryParams);

const suffixPath = suffix ? `/${suffix}` : "";
const suffixPath = modifiedSuffix ? `/${modifiedSuffix}` : "";

const hashPath = hash ? `#${hash}` : "";

Expand Down
13 changes: 13 additions & 0 deletions app/client/src/ce/pages/Applications/WorkspaceMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,19 @@ function WorkspaceMenu({
workspaceId={workspace.id}
workspacePermissions={workspace.userPermissions || []}
/>
{hasManageWorkspacePermissions && (
<CustomMenuItem
className="workspace-menu-item"
onClick={() =>
getOnSelectAction(DropdownOnSelectActions.REDIRECT, {
path: `/workspace/${workspace.id}/datasources`,
})
}
>
<Icon name="database-2-line" size="md" />
Datasources
</CustomMenuItem>
)}
{canInviteToWorkspace && (
<CustomMenuItem
className="error-menuitem workspace-menu-item"
Expand Down
10 changes: 10 additions & 0 deletions app/client/src/ce/reducers/uiReducers/editorReducer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { UpdateCanvasPayload } from "actions/pageActions";
export const initialState: EditorReduxState = {
widgetConfigBuilt: false,
initialized: false,
isWorkspaceEditorInitialized: false,
loadingStates: {
publishing: false,
publishingError: false,
Expand Down Expand Up @@ -65,6 +66,14 @@ export const handlers = {
[ReduxActionTypes.INITIALIZE_EDITOR_SUCCESS]: (state: EditorReduxState) => {
return { ...state, initialized: true };
},
[ReduxActionTypes.INITIALIZE_WORKSPACE_IDE]: (state: EditorReduxState) => {
return { ...state, isWorkspaceEditorInitialized: false };
},
[ReduxActionTypes.INITIALIZE_WORKSPACE_IDE_SUCCESS]: (
state: EditorReduxState,
) => {
return { ...state, isWorkspaceEditorInitialized: true };
},
Comment on lines +69 to +76
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 | 🟠 Major

Handle initialization error to avoid infinite spinner.

Add INITIALIZE_WORKSPACE_IDE_ERROR handler and store the error so UI can show a fallback.

   [ReduxActionTypes.INITIALIZE_WORKSPACE_IDE]: (state: EditorReduxState) => {
     return { ...state, isWorkspaceEditorInitialized: false };
   },
   [ReduxActionTypes.INITIALIZE_WORKSPACE_IDE_SUCCESS]: (
     state: EditorReduxState,
   ) => {
     return { ...state, isWorkspaceEditorInitialized: true };
   },
+  [ReduxActionErrorTypes.INITIALIZE_WORKSPACE_IDE_ERROR]: (
+    state: EditorReduxState,
+    action: ReduxAction<{ error: unknown }>,
+  ) => {
+    return {
+      ...state,
+      isWorkspaceEditorInitialized: false,
+      workspaceEditorInitError: action.payload?.error,
+    };
+  },

Additionally add the field to state shape and initial state:

 // initialState
   onLoadActionExecution: false,
+  workspaceEditorInitError: undefined,
 export interface EditorReduxState {
 ...
   onLoadActionExecution?: boolean;
+  workspaceEditorInitError?: unknown;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[ReduxActionTypes.INITIALIZE_WORKSPACE_IDE]: (state: EditorReduxState) => {
return { ...state, isWorkspaceEditorInitialized: false };
},
[ReduxActionTypes.INITIALIZE_WORKSPACE_IDE_SUCCESS]: (
state: EditorReduxState,
) => {
return { ...state, isWorkspaceEditorInitialized: true };
},
[ReduxActionTypes.INITIALIZE_WORKSPACE_IDE]: (state: EditorReduxState) => {
return { ...state, isWorkspaceEditorInitialized: false };
},
[ReduxActionTypes.INITIALIZE_WORKSPACE_IDE_SUCCESS]: (
state: EditorReduxState,
) => {
return { ...state, isWorkspaceEditorInitialized: true };
},
[ReduxActionErrorTypes.INITIALIZE_WORKSPACE_IDE_ERROR]: (
state: EditorReduxState,
action: ReduxAction<{ error: unknown }>,
) => {
return {
...state,
isWorkspaceEditorInitialized: false,
workspaceEditorInitError: action.payload?.error,
};
},
🤖 Prompt for AI Agents
In app/client/src/ce/reducers/uiReducers/editorReducer.tsx around lines 69 to
76, add a new ReduxActionTypes.INITIALIZE_WORKSPACE_IDE_ERROR case that sets
isWorkspaceEditorInitialized to false and stores the error payload on state
(e.g., workspaceInitError or initializeWorkspaceError) so the UI can stop the
spinner and show a fallback; also add that error field to the EditorReduxState
type/shape and initialize it in the reducer's initial state (null or undefined
by default), and ensure the SUCCESS and INITIALIZE handlers clear/reset the
error.

[ReduxActionTypes.UPDATE_PAGE_SUCCESS]: (
state: EditorReduxState,
action: ReduxAction<UpdatePageResponse>,
Expand Down Expand Up @@ -318,6 +327,7 @@ const editorReducer = createReducer(initialState, handlers);
export interface EditorReduxState {
widgetConfigBuilt: boolean;
initialized: boolean;
isWorkspaceEditorInitialized: boolean;
pageWidgetId?: string;
currentLayoutId?: string;
currentPageName?: string;
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 @@ -57,6 +57,7 @@ import PostEvaluationSagas from "sagas/PostEvaluationSagas";
/* Sagas that are registered by a module that is designed to be independent of the core platform */
import ternSagas from "sagas/TernSaga";
import gitApplicationSagas from "git-artifact-helpers/application/sagas";
import workspaceIDESagas from "ee/sagas/workspaceIDESagas";

export const sagas = [
initSagas,
Expand All @@ -75,6 +76,7 @@ export const sagas = [
templateSagas,
pluginSagas,
workspaceSagas,
workspaceIDESagas,
curlImportSagas,
snipingModeSagas,
queryPaneSagas,
Expand Down
43 changes: 43 additions & 0 deletions app/client/src/ce/sagas/workspaceIDESagas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { all, put, call, takeLatest } from "redux-saga/effects";

import {
ReduxActionErrorTypes,
ReduxActionTypes,
} from "ee/constants/ReduxActionConstants";
import WorkspaceEditorEngine from "ee/entities/Engine/WorkspaceEditorEngine";
import type { ReduxAction } from "actions/ReduxActionTypes";
import type { InitWorkspaceIDEPayload } from "ee/actions/workspaceIDEActions";
import { resetEditorRequest } from "actions/initActions";

export function* startWorkspaceIDE(
action: ReduxAction<InitWorkspaceIDEPayload>,
) {
try {
const workspaceEngine = new WorkspaceEditorEngine();
const { workspaceId } = action.payload;

/**
* During editor switches like app (view mode) -> workspace
* there are certain cases were stale data stays in reducers.
* This ensures a clean state of reducers and avoids any dependency
*/
yield put(resetEditorRequest());
yield call(workspaceEngine.setupEngine);
yield call(workspaceEngine.loadWorkspace, workspaceId);
yield call(workspaceEngine.loadPluginsAndDatasources, workspaceId);
yield call(workspaceEngine.completeChore);
} catch (error) {
yield put({
type: ReduxActionErrorTypes.INITIALIZE_WORKSPACE_IDE_ERROR,
payload: {
error,
},
});
}
Comment on lines +27 to +33
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 | 🟠 Major

Surface error state for UI.

INITIALIZE_WORKSPACE_IDE_ERROR is dispatched, but there’s no reducer handling to unblock or show an error. Pair this with a reducer flag and UI fallback. See reducer comment for concrete diff.

🤖 Prompt for AI Agents
In app/client/src/ce/sagas/workspaceIDESagas.ts around lines 30 to 36, the saga
dispatches INITIALIZE_WORKSPACE_IDE_ERROR but the app has no reducer or state
flag to surface this error to the UI; add a reducer case and state flag (e.g.,
workspaceIDEInitializationError or isWorkspaceIDEInitError) to store the error
payload and clear any "initializing" flag, update the reducer's initial state
accordingly, and ensure components read this flag to show a fallback/error UI
and stop loading spinners; also add an action handler to clear the error when
retrying or on successful init.

}

export default function* watchWorkspaceIDESagas() {
yield all([
takeLatest(ReduxActionTypes.INITIALIZE_WORKSPACE_IDE, startWorkspaceIDE),
]);
}
1 change: 1 addition & 0 deletions app/client/src/constants/routes/baseRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const WORKSPACE_INVITE_USERS_PAGE_URL = `${WORKSPACE_URL}/invite`;
export const WORKSPACE_SETTINGS_PAGE_URL = `${WORKSPACE_URL}/settings`;
export const WORKSPACE_SETTINGS_GENERAL_PAGE_URL = `${WORKSPACE_URL}/settings/general`;
export const WORKSPACE_SETTINGS_MEMBERS_PAGE_URL = `${WORKSPACE_URL}/settings/members`;
export const WORKSPACE_DATASOURCES_PAGE_URL = `${WORKSPACE_URL}/datasources`;
export const WORKSPACE_SETTINGS_LICENSE_PAGE_URL = `/settings/license`;
export const ORG_LOGIN_PATH = "/org";

Expand Down
1 change: 1 addition & 0 deletions app/client/src/ee/actions/workspaceIDEActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "ce/actions/workspaceIDEActions";
1 change: 1 addition & 0 deletions app/client/src/ee/entities/Engine/WorkspaceEditorEngine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "ce/entities/Engine/WorkspaceEditorEngine";
2 changes: 2 additions & 0 deletions app/client/src/ee/sagas/workspaceIDESagas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "ce/sagas/workspaceIDESagas";
export { default } from "ce/sagas/workspaceIDESagas";
Loading
Loading