Skip to content

Comments

feat: add favorite applications (V2)#41555

Open
salevine wants to merge 26 commits intoreleasefrom
feat/8479/add-favorite-applications_v2
Open

feat: add favorite applications (V2)#41555
salevine wants to merge 26 commits intoreleasefrom
feat/8479/add-favorite-applications_v2

Conversation

@salevine
Copy link
Contributor

@salevine salevine commented Feb 13, 2026

Description

Feature Request 8479: Allow a user to favorite applications.

In bigger factories there can be many workspaces and applications. Users might have access to a lot of applications, but generally only use a handful. This feature allows a user to favorite one or more applications (up to 50). Any favorited applications will show up in a virtual Favorites workspace in alphabetical order

Fixes #8479

Automation

/ok-to-test tags="@tag.All"

🔍 Cypress test results

Tip

🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
Workflow run: https://github.com/appsmithorg/appsmith/actions/runs/22001834951
Commit: 56d760c
Cypress dashboard.
Tags: @tag.All
Spec:


Fri, 13 Feb 2026 21:58:17 UTC

Communication

Should the DevRel and Marketing teams inform users about this change?

  • Yes
  • No

Summary by CodeRabbit

  • New Features

    • Mark applications as favorites via a heart toggle on application cards (limit: 50)
    • Virtual "Favorites" workspace and dedicated view listing favorited applications
    • Endpoints and flows to fetch/sync favorite applications
  • UX

    • Favorites reflected across listings, headers, and workspace navigation (URL support)
    • Optimistic toggles with rollback on error and toast feedback; auto-refresh of favorites when workspaces change
  • Chores

    • Deleted applications are removed from users' favorites automatically

salevine and others added 21 commits February 12, 2026 12:28
Fixed FavitoristesSagas to put in the generic directory
…-push hook blocking

The favorite actions are now in src/actions/applicationActions and are imported directly, so we don't need to re-export them from ee/actions/applicationActions.ts
Addressing the backend comments with fixes
Fixed. Added UserDataService to the ApplicationPageServiceImpl constructor and passed it to the super call. The compilation error should be resolved.
- UserData: atomic toggle favorites (addToSet/pull) to fix race condition
- FavoritesSagas: remove unreachable/error action, use ApiResponse types
- Remove toggleFavoriteApplicationError and TOGGLE_FAVORITE_APPLICATION_ERROR
- Card: drop redundant overlay pointerEvents (handled by Wrapper)
- WorkspaceSagas: refetch favorites when loading workspace (drop inaccessible)
- ApplicationPageServiceCEImpl: use getBaseId() directly for favoriteId

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ew on refresh

- In ApplicationSagas and InitSagas: when a logged-in user hits 404 for an app (e.g. deleted favorite), redirect to applications?workspaceId=__favorites__, refetch favorites, and show error toast instead of crash page
- Applications page: prepend Favorites workspace when URL has workspaceId=__favorites__ (not only when hasFavorites) so redirect lands on Favorites
- Applications page: when active workspace is __favorites__ but not in list yet, use DEFAULT_FAVORITES_WORKSPACE and still dispatch SET_CURRENT_WORKSPACE and fetchEntitiesOfWorkspace so Favorites view loads correctly after refresh

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… action

Co-authored-by: Cursor <cursoragent@cursor.com>
…e favorites loading state

Move userPermissions enrichment from ApplicationCard into FavoritesSagas
so the component no longer queries the global application list to resolve
permissions for favorite apps. This removes a tight coupling between a
presentational component and unrelated global state.

Also introduce a dedicated isFetchingFavoriteApplications loading flag in
selectedWorkspaceReducer so favorites fetches no longer reuse the
workspace's isFetchingApplications flag, making Redux state unambiguous
during debugging.

Co-authored-by: Cursor <cursoragent@cursor.com>
@salevine salevine requested a review from sharat87 as a code owner February 13, 2026 15:12
@salevine salevine added Enhancement New feature or request ok-to-test Required label for CI labels Feb 13, 2026
@github-actions github-actions bot removed the Enhancement New feature or request label Feb 13, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an end-to-end "Favorites" feature: UI heart toggle and virtual Favorites workspace, Redux actions/reducers/selectors, sagas with optimistic toggle and 50-item limit, new backend endpoints/repository/service support, and persistence of users' favorite application IDs.

Changes

Cohort / File(s) Summary
Redux Actions & Constants
app/client/src/actions/applicationActions.ts, app/client/src/ce/constants/ReduxActionConstants.tsx
Add action creators and lifecycle/error constants for toggling and fetching favorite applications (init, success, error).
Frontend API & Types
app/client/src/ce/api/ApplicationApi.tsx, app/client/src/entities/Application/types.ts
Add toggleFavoriteApplication and getFavoriteApplications API methods; add isFavorited?: boolean to ApplicationPayload.
Workspace Constants & Models
app/client/src/ce/constants/workspaceConstants.ts
Introduce FAVORITES_KEY, DEFAULT_FAVORITES_WORKSPACE, and optional Workspace.isVirtual for the virtual Favorites workspace.
Frontend Reducers & Selectors
app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx, app/client/src/ce/reducers/uiReducers/selectedWorkspaceReducer.ts, app/client/src/ce/selectors/applicationSelectors.tsx, app/client/src/ce/selectors/selectedWorkspaceSelectors.ts
Track favoriteApplicationIds and fetching flags; add reducers for toggle/fetch lifecycles; add memoized selectors exposing favorite IDs, filtered apps, and isFavorited augmentation.
Frontend Sagas & Initialization
app/client/src/sagas/FavoritesSagas.ts, app/client/src/ce/sagas/WorkspaceSagas.ts, app/client/src/ce/sagas/index.tsx, app/client/src/sagas/InitSagas.ts
New favorites sagas (optimistic toggle with rollback on error, 50-favorite cap, fetch with enrichment), register saga listener, treat virtual favorites workspace in workspace sagas, and redirect/init favorites in InitSagas on certain not-found flows.
Frontend UI & Pages
app/client/src/components/common/Card.tsx, app/client/src/pages/Applications/ApplicationCard.tsx, app/client/src/ce/pages/Applications/index.tsx
Card gains isFavorited/onToggleFavorite, renders heart toggle; ApplicationCard dispatches toggle; Applications page injects/renders virtual Favorites workspace entry.
Frontend Utilities & Hooks
app/client/src/pages/AppIDE/layouts/components/Header/index.tsx, app/client/src/pages/AppIDE/layouts/hooks/useWidgetSelectionBlockListener.ts, app/client/src/components/editorComponents/Debugger/index.tsx
Header link now includes workspaceId query param; refine useEffect dependencies to prevent spurious re-dispatches.
Backend Controllers & Domain
app/server/.../controllers/ce/UserControllerCE.java, app/server/.../domains/UserData.java
Add endpoints: PUT /applications/{applicationId}/favorite and GET /favoriteApplications; add favoriteApplicationIds to UserData.
Backend Repository & Bridge
app/server/.../repositories/ce/CustomUserDataRepositoryCE.java, app/server/.../repositories/ce/CustomUserDataRepositoryCEImpl.java, app/server/.../helpers/ce/bridge/BridgeUpdate.java
Add repository methods for atomic add/remove and conditional add-under-limit; add BridgeUpdate.addToSet helper.
Backend Services
app/server/.../services/ce/UserDataServiceCE.java, app/server/.../services/ce/UserDataServiceCEImpl.java, app/server/.../services/ce/ApplicationPageServiceCEImpl.java, app/server/.../services/ApplicationPageServiceImpl.java, app/server/.../services/UserDataServiceImpl.java
Service interface and implementations for toggle/get/remove favorites, permission checks, 50-item limit enforcement, removal of deleted apps from all favorites, and constructor wiring updates for new dependencies.

Sequence Diagram(s)

sequenceDiagram
    participant User as User (Client)
    participant FrontendUI as Frontend UI
    participant FrontendSaga as Favorites Saga
    participant BackendAPI as Backend API
    participant DB as Database

    User->>FrontendUI: Click heart icon (toggle)
    FrontendUI->>FrontendSaga: Dispatch TOGGLE_FAVORITE_APPLICATION_INIT (optimistic)
    FrontendSaga->>FrontendUI: Dispatch TOGGLE_FAVORITE_APPLICATION_SUCCESS (optimistic UI update)
    FrontendSaga->>BackendAPI: PUT /applications/{appId}/favorite
    BackendAPI->>DB: Atomic addToSet or pull favoriteApplicationIds
    DB-->>BackendAPI: Updated user favorite state
    BackendAPI-->>FrontendSaga: Return success or error
    alt success
        FrontendSaga->>FrontendUI: Confirm success (no change)
    else error
        FrontendSaga->>FrontendUI: Dispatch TOGGLE_FAVORITE_APPLICATION_ERROR, revert optimistic update, show toast
    end
Loading
sequenceDiagram
    participant User as User (Client)
    participant FrontendUI as Frontend UI
    participant FrontendSaga as Favorites Saga
    participant BackendAPI as Backend API
    participant AppRepo as Application Repository

    User->>FrontendUI: Navigate to Favorites workspace
    FrontendUI->>FrontendSaga: Dispatch FETCH_FAVORITE_APPLICATIONS_INIT
    FrontendSaga->>BackendAPI: GET /favoriteApplications
    BackendAPI->>AppRepo: Load application details for favorite IDs
    AppRepo-->>BackendAPI: Applications with metadata
    BackendAPI->>BackendAPI: Enrich with permissions/defaultPageId
    BackendAPI-->>FrontendSaga: Return List<Application>
    FrontendSaga->>FrontendUI: Dispatch FETCH_FAVORITE_APPLICATIONS_SUCCESS and render list
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

❤️ Heart clicks, sagas hum in time,
Fifty kept in a virtual clime,
Actions, reducers, backend and DB,
A favorites workspace — swift and free,
Tiny hearts, big sync — cheers to the rhyme!

🚥 Pre-merge checks | ✅ 3 | ❌ 3
❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Linked issue #8479 is about form field components (Text, Number, Phone, Email, Switch, etc.), but PR implements favorite applications feature—completely different scope. Verify the correct issue link. This PR should reference issue #8479 about favorite applications, not form field components. Confirm linked issue number.
Docstring Coverage ⚠️ Warning Docstring coverage is 43.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (39 files):

⚔️ Dockerfile (content)
⚔️ app/client/docker/templates/nginx-app.conf.template (content)
⚔️ app/client/jest.config.js (content)
⚔️ app/client/package.json (content)
⚔️ app/client/public/index.html (content)
⚔️ app/client/src/ce/api/ApplicationApi.tsx (content)
⚔️ app/client/src/ce/configs/index.ts (content)
⚔️ app/client/src/ce/configs/types.ts (content)
⚔️ app/client/src/ce/constants/ReduxActionConstants.tsx (content)
⚔️ app/client/src/ce/constants/messages.ts (content)
⚔️ app/client/src/ce/constants/workspaceConstants.ts (content)
⚔️ app/client/src/ce/pages/Applications/index.tsx (content)
⚔️ app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx (content)
⚔️ app/client/src/ce/reducers/uiReducers/selectedWorkspaceReducer.ts (content)
⚔️ app/client/src/ce/sagas/WorkspaceSagas.ts (content)
⚔️ app/client/src/ce/sagas/index.tsx (content)
⚔️ app/client/src/ce/selectors/applicationSelectors.tsx (content)
⚔️ app/client/src/ce/selectors/selectedWorkspaceSelectors.ts (content)
⚔️ app/client/src/components/common/Card.tsx (content)
⚔️ app/client/src/components/editorComponents/Debugger/index.tsx (content)
⚔️ app/client/src/entities/Application/types.ts (content)
⚔️ app/client/src/pages/AppIDE/layouts/components/Header/index.tsx (content)
⚔️ app/client/src/pages/AppIDE/layouts/hooks/useWidgetSelectionBlockListener.ts (content)
⚔️ app/client/src/pages/Applications/ApplicationCard.tsx (content)
⚔️ app/client/src/pages/Editor/HelpButton.tsx (content)
⚔️ app/client/src/pages/common/SearchBar/HomepageHeaderAction.tsx (content)
⚔️ app/client/src/sagas/InitSagas.ts (content)
⚔️ app/client/yarn.lock (content)
⚔️ app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java (content)
⚔️ app/server/appsmith-server/src/main/java/com/appsmith/server/domains/UserData.java (content)
⚔️ app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/bridge/BridgeUpdate.java (content)
⚔️ app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserDataRepositoryCE.java (content)
⚔️ app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserDataRepositoryCEImpl.java (content)
⚔️ app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java (content)
⚔️ app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataServiceImpl.java (content)
⚔️ app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java (content)
⚔️ app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserDataServiceCE.java (content)
⚔️ app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserDataServiceCEImpl.java (content)
⚔️ scripts/deploy_preview.sh (content)

These conflicts must be resolved before merging into release.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly and concisely describes the main feature: adding favorite applications functionality (V2 indicates iteration).
Out of Scope Changes check ✅ Passed All changes are cohesive and focused on implementing the favorite applications feature across frontend and backend without unrelated modifications.
Description check ✅ Passed PR description covers the feature objective, issue reference, automation, test results, and DevRel communication decision. All key sections from the template are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/8479/add-favorite-applications_v2
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch feat/8479/add-favorite-applications_v2
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java (1)

577-587: ⚠️ Potential issue | 🟡 Minor

Add null guard for favoriteId to match defensive patterns used elsewhere in the codebase.

The code calls removeApplicationFromFavorites(favoriteId) without validating that application.getBaseId() returns a non-null value. While the repository method handles null gracefully, the codebase uses defensive null checks for getBaseId() in other places (e.g., StaticUrlAnalyticsUtils.java). Add a null check to fall back to application.getId() if needed.

Proposed fix
-        String favoriteId = application.getBaseId();
+        String favoriteId = application.getBaseId() != null ? application.getBaseId() : application.getId();
🤖 Fix all issues with AI agents
In `@app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx`:
- Around line 886-917: The toggle reducer
(ReduxActionTypes.TOGGLE_FAVORITE_APPLICATION_SUCCESS) mixes baseId and id into
favoriteApplicationIds causing mismatches with
FETCH_FAVORITE_APPLICATIONS_SUCCESS which uses app.id; fix by normalizing to the
canonical application.id before mutating favoriteApplicationIds: inside the
TOGGLE_FAVORITE_APPLICATION_SUCCESS handler, look up the app in
state.applicationList by matching (app.baseId || app.id) === applicationId to
obtain the canonical app.id (fall back to the incoming applicationId if no
match), then add/remove that canonical id in favoriteApplicationIds and still
update applicationList entries by matching (app.baseId || app.id) ===
applicationId to set isFavorited. This ensures favoriteApplicationIds and
FETCH_FAVORITE_APPLICATIONS_SUCCESS both use the same id space.

In `@app/client/src/ce/sagas/ApplicationSagas.tsx`:
- Around line 435-445: Replace the hardcoded toast string in the error branch
(the block that checks currentUser, ERROR_CODES.PAGE_NOT_FOUND and calls
toast.show) with a call to createMessage using a shared message constant (e.g.,
APPLICATION_NOT_FOUND) instead of the literal "Application not found or
deleted."; add that constant to the common messages module already used by
InitSagas.ts and import it into ApplicationSagas.tsx, then call
toast.show(createMessage(APPLICATION_NOT_FOUND), { kind: "error" }) so both
sagas use the same i18n-friendly message.

In `@app/client/src/sagas/FavoritesSagas.ts`:
- Around line 86-95: The permissions map is built from a search-filtered list
because the saga uses select(getApplicationList); change it to use
select(getApplications) so you get the unfiltered application list. In the saga
code that populates permissionsById (currently reading allApplications from
select(getApplicationList)), replace that select call with
select(getApplications) and keep the subsequent loop that checks
app.userPermissions and sets permissionsById for app.id unchanged so permissions
for non-search-matching apps are included.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java`:
- Around line 216-222: Controller mapping for toggleFavoriteApplication is using
"/applications/{applicationId}/favorite" but endpoints should live under
Url.USER_URL; change the mapping in UserControllerCE to include the user prefix
(use Url.USER_URL) and accept the userId path variable so the route matches
client expectations (e.g., replace
`@PutMapping`("/applications/{applicationId}/favorite") with a mapping that
composes Url.USER_URL + "/{userId}/applications/{applicationId}/favorite"), add
a `@PathVariable` String userId parameter to toggleFavoriteApplication, and
pass/forward that userId to userDataService as needed; also scan other
UserControllerCE methods for similar mappings and align them with Url.USER_URL
(or alternatively update ApplicationApi to call the users-prefixed path if you
prefer changing the client).

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserDataServiceCEImpl.java`:
- Around line 437-491: The in-memory size check in toggleFavoriteApplication can
race with the atomic addToSet and allow exceeding
MAX_FAVORITE_APPLICATIONS_LIMIT; remove that local check and perform a
conditional atomic update instead. Implement a repository method (e.g.,
addFavoriteApplicationForUserIfUnderLimit or addFavoriteApplicationWithMaxLimit)
that issues a single DB update which both $addToSet the applicationId and
enforces the max size (using a Mongo conditional update/filter or aggregation
expression to require array length < MAX_FAVORITE_APPLICATIONS_LIMIT), return a
signal indicating success vs failure (no-op vs error), and call that new repo
method from toggleFavoriteApplication (keeping the existing new-user save branch
for userData.getId() == null). Ensure toggleFavoriteApplication surfaces an
error when the conditional update indicates the limit was reached.
🧹 Nitpick comments (7)
app/client/src/ce/sagas/ApplicationSagas.tsx (1)

435-445: Duplicate redirect-to-favorites logic in InitSagas.ts.

The same history.replace + FETCH_FAVORITE_APPLICATIONS_INIT + toast.show pattern appears in both handleFetchApplicationError (here) and startAppEngine (in InitSagas.ts, lines 425-436). Consider extracting a shared helper to avoid drift between the two.

app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java (1)

228-234: Consider whether returning full Application objects is necessary.

getFavoriteApplications returns List<Application> — the domain object may include fields beyond what the client needs. While @JsonView(Views.Public.class) filters the serialization, a lighter DTO projection could reduce payload size if the favorites list grows toward the 50-app cap.

app/client/src/ce/pages/Applications/index.tsx (1)

31-42: Consolidate imports from the same module.

getHasFavorites (line 38) is imported separately from the same ee/selectors/applicationSelectors module already imported on lines 31-37. Merge them into a single import statement.

♻️ Proposed fix
 import {
   getApplicationList,
   getApplicationSearchKeyword,
   getCreateApplicationError,
   getCurrentApplicationIdForCreateNewApp,
   getIsCreatingApplication,
   getIsDeletingApplication,
+  getHasFavorites,
 } from "ee/selectors/applicationSelectors";
-import { getHasFavorites } from "ee/selectors/applicationSelectors";
app/client/src/components/common/Card.tsx (1)

421-430: Add aria-pressed to the toggle button for screen readers.

The button has a static aria-label="Toggle favorite" but doesn't communicate the current state. Toggle buttons should use aria-pressed so assistive technologies announce whether the item is currently favorited.

♿ Proposed fix
           <FavoriteIconWrapper
             aria-label="Toggle favorite"
+            aria-pressed={!!isFavorited}
             data-testid="t--favorite-icon"
             onClick={handleFavoriteClick}
             type="button"
           >
app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx (1)

910-917: FETCH_FAVORITE_APPLICATIONS_SUCCESS does not update isFavorited on applicationList entries.

The toggle handler (line 897–901) correctly sets isFavorited on matching applicationList entries, but this fetch handler only populates favoriteApplicationIds without syncing the flag back to applicationList. Any consumer reading app.isFavorited directly from the application list (rather than through the memoized selector in selectedWorkspaceSelectors.ts) will see stale data after a favorites fetch.

Consider iterating over applicationList here to set isFavorited based on the fetched IDs, matching what the toggle handler already does.

♻️ Proposed fix
   [ReduxActionTypes.FETCH_FAVORITE_APPLICATIONS_SUCCESS]: (
     state: ApplicationsReduxState,
     action: ReduxAction<ApplicationPayload[]>,
-  ) => ({
-    ...state,
-    isFetchingFavorites: false,
-    favoriteApplicationIds: action.payload.map((app) => app.id),
-  }),
+  ) => {
+    const favoriteApplicationIds = action.payload.map((app) => app.id);
+    const favoriteIdSet = new Set(favoriteApplicationIds);
+
+    return {
+      ...state,
+      isFetchingFavorites: false,
+      favoriteApplicationIds,
+      applicationList: state.applicationList.map((app) => ({
+        ...app,
+        isFavorited: favoriteIdSet.has(app.id),
+      })),
+    };
+  },
app/client/src/ce/selectors/selectedWorkspaceSelectors.ts (1)

32-48: Consistent update — the OR logic here mirrors getIsFetchingApplications above. Consider extracting the shared condition to a single selector to avoid drift, but low priority.

app/client/src/ce/reducers/uiReducers/selectedWorkspaceReducer.ts (1)

272-290: Correct toggle behavior for favorites vs. regular workspaces.

Minor Immer style nit: the else branch on line 284 uses .map() which allocates a new array. Since this is an Immer reducer, you can mutate in place:

♻️ Idiomatic Immer alternative
     } else {
-      draftState.applications = draftState.applications.map((app) =>
-        (app.baseId || app.id) === applicationId
-          ? { ...app, isFavorited }
-          : app,
-      );
+      const app = draftState.applications.find(
+        (app) => (app.baseId || app.id) === applicationId,
+      );
+      if (app) {
+        app.isFavorited = isFavorited;
+      }
     }

…limit atomically

Client API methods were hitting the wrong controller (ApplicationController
instead of UserController). The toggle reducer was mixing baseId/id into
favoriteApplicationIds causing mismatches with the fetch reducer. The
in-memory size check in toggleFavoriteApplication could race with
concurrent addToSet calls, allowing the limit to be exceeded.

- Point client favorites API calls to v1/users/... to match UserControllerCE
- Resolve canonical app.id in TOGGLE_FAVORITE_APPLICATION_SUCCESS before
  mutating favoriteApplicationIds so it stays consistent with FETCH
- Add addFavoriteApplicationForUserIfUnderLimit repo method using a
  conditional MongoDB update (array-index existence trick + $addToSet)
- Replace in-memory size check with atomic DB-level enforcement

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserDataServiceCEImpl.java`:
- Around line 466-479: The new-user branch in UserDataServiceCEImpl that checks
userData.getId() == null is vulnerable to a race where two concurrent requests
both save a transient UserData and create duplicates; instead implement an
atomic upsert or handle DuplicateKeyException and retry using the existing-user
conditional update path. Concretely, replace the repository.save(userData) call
in that branch with an upsert/atomic push (e.g., use a repository.upsert or
MongoTemplate.findAndModify with upsert=true and a $addToSet/$push plus size
check against MAX_FAVORITE_APPLICATIONS_LIMIT), or catch DuplicateKeyException
from repository.save and then invoke the same conditional update logic used for
the existing-user path to add the favorite id; ensure you reference
UserDataServiceCEImpl, repository.save, and the existing-user conditional update
code when making the change.
- Around line 437-453: The in-memory favorites.contains(applicationId) check in
toggleFavoriteApplication can be stale; instead, avoid making the add/remove
decision from the snapshot and perform atomic repository operations to determine
the outcome: call repository.removeFavoriteApplicationForUser(user.getId(),
applicationId) first and inspect its result (e.g., modified count / boolean) —
if it reports a modification return getForUser(user.getId()), otherwise call
repository.addFavoriteApplicationForUser(user.getId(), applicationId) and then
return getForUser(user.getId()); update toggleFavoriteApplication to rely on the
repository operation results rather than favorites.contains and use the existing
getForUser/sessionUserService calls to return the updated UserData.
🧹 Nitpick comments (3)
app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomUserDataRepositoryCEImpl.java (2)

50-56: Bulk removal across all users — no criteria filter is intentional but worth noting.

queryBuilder().updateAll(update) with no .criteria(...) will scan and update every UserData document. This is fine for a cleanup-on-delete path (infrequent), but be aware it won't use an index efficiently. If the favoriteApplicationIds field is sparsely populated, the write amplification should be minimal.


58-66: Remove unused addFavoriteApplicationForUser method.

The service layer calls only addFavoriteApplicationForUserIfUnderLimit for existing users and repository.save() for new users. This method serves no purpose and should be removed to reduce code clutter.

app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserDataServiceCEImpl.java (1)

466-495: Duplicated error message string — extract a constant.

The limit-reached message is duplicated on lines 472–474 and 489–491. Extract it to a private static final String to keep them in sync.

Suggested refactor
+ private static final String FAVORITE_LIMIT_REACHED_MESSAGE =
+         "Maximum favorite applications limit (%d) reached. Please remove some favorites before adding new ones.";
+
  // Then in both locations:
- String.format(
-         "Maximum favorite applications limit (%d) reached. Please remove some favorites before adding new ones.",
-         MAX_FAVORITE_APPLICATIONS_LIMIT)
+ String.format(FAVORITE_LIMIT_REACHED_MESSAGE, MAX_FAVORITE_APPLICATIONS_LIMIT)

@salevine salevine changed the title Working on favorites application and virtual workspace feat: add favorite applications (V2) Feb 13, 2026
@github-actions github-actions bot added the Enhancement New feature or request label Feb 13, 2026
salevine and others added 4 commits February 13, 2026 12:29
The blanket redirect of all logged-in-user 404s to the favorites page
broke the standard error page (`.t--error-page-title`) for users who
simply lack access to an app. This caused the ShareAppTests_Spec
"validate public access disable" test to fail.

- InitSagas: check getFavoriteApplicationIds before redirecting; only
  redirect if the app is actually in the user's favorites list,
  otherwise fall through to safeCrashAppRequest (error page)
- ApplicationSagas: restore original handleFetchApplicationError
  behavior — anonymous users get the crash page, logged-in users get
  FETCH_APPLICATION_ERROR + FETCH_PAGE_LIST_ERROR (error page)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The in-memory favorites.contains() check was stale under concurrency,
and the new-user repository.save() could race to create duplicate
UserData documents.

- removeFavoriteApplicationForUser now returns Mono<Integer> and adds
  an array-contains criteria so matchedCount signals whether a removal
  actually occurred
- toggleFavoriteApplication for existing users: tries atomic remove
  first, inspects the DB result, then falls back to atomic add — no
  in-memory snapshot decision
- toggleFavoriteApplication for new users: both save() calls catch
  DuplicateKeyException and retry through the atomic existing-user
  path (removeFavoriteApplicationForUser / addFavoriteApplicationFor-
  UserIfUnderLimit)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
getApplicationList is filtered by the active search keyword, so the
permissions map built in fetchFavoriteApplicationsSaga could miss apps
that don't match the current search query. Use getApplications instead
to include all loaded apps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cator

Favorites fetch was OR'd into getIsFetchingApplications, causing app
cards to flash/disappear when the background favorites refresh set
isFetchingFavoriteApplications=true after entity loading completed.
Now only the Favorites virtual workspace shows a skeleton during
favorites fetch; regular workspaces load favorites silently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
applicationId: string,
): Promise<AxiosPromise<ApiResponse>> {
return Api.put(`v1/users/applications/${applicationId}/favorite`);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

@salevine This endpoint seems wrong; shouldn't it be
v1/applications/${applicationId}/favorite and the below one as well?
I remember this to be correct earlier, you used ApplicationApi.baseURL; something changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This happened when I was creating the new PR and then trying to fix the various code rabbit identified issues and getting the tests to pass. I correct this this.

@salevine
Copy link
Contributor Author

/build-deploy-preview skip-tests=true

@github-actions
Copy link

Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/22067006626.
Workflow: On demand build Docker image and deploy preview.
skip-tests: true.
env: ``.
PR: 41555.
recreate: .

@github-actions
Copy link

Deploy-Preview-URL: https://ce-41555.dp.appsmith.com

@ashit-rath ashit-rath removed the request for review from sharat87 February 18, 2026 04:30
@ashit-rath ashit-rath mentioned this pull request Feb 18, 2026
2 tasks
@salevine salevine requested a review from ashit-rath February 23, 2026 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request ok-to-test Required label for CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants