Skip to content

fix(uikit-playground): fix project and screen state update after deletion#39241

Open
AbishekRaj2007 wants to merge 2 commits intoRocketChat:developfrom
AbishekRaj2007:develop
Open

fix(uikit-playground): fix project and screen state update after deletion#39241
AbishekRaj2007 wants to merge 2 commits intoRocketChat:developfrom
AbishekRaj2007:develop

Conversation

@AbishekRaj2007
Copy link

@AbishekRaj2007 AbishekRaj2007 commented Mar 2, 2026

Proposed Changes

This PR addresses a state management bug in the UIKit Playground where deleting a screen or project failed to correctly update activeScreen and activeProject in the application state.

Root Cause:
The reducer was recalculating active state (determining which screen/project should be selected after deletion) before removing the target screen from the list. This meant the recalculation operated on stale data — the deleted item was still present in state when the new active indices were being resolved, leading to the UI pointing to an incorrect or non-existent screen/project.

Changes Made:

  • Reordered reducer logic so that screen removal is performed first, then active state is recalculated against the updated (post-deletion) screen list.
  • Added implicit project deletion: if removing a screen results in a project having zero remaining screens, the project is automatically removed from state. This prevents orphaned, empty projects from persisting in the UI.
  • Active state resolution now always derives from accurate, current state — ensuring activeScreen and activeProject reflect a valid selection after any deletion operation.

No new dependencies were introduced. The changes are isolated to the reducer logic handling deletion actions.


Issue(s)

Closes #39240

Summary: Deleting a screen or project in UIKit Playground left activeScreen and/or activeProject pointing to stale or invalid state, causing the UI to display incorrect data or fail to update visually after deletion.


Steps to Test or Reproduce

To reproduce the bug (before this fix):

  1. Open the UIKit Playground.
  2. Create a project with two or more screens.
  3. Delete the currently active screen.
  4. Observe: activeScreen does not update correctly — the UI may still reference the deleted screen or show an incorrect selection.
  5. Delete all screens from a project.
  6. Observe: the project remains in state as an empty entry.

To verify the fix:

  1. Create a project with multiple screens.
  2. Delete the active screen → confirm the UI selects a valid adjacent screen and state is consistent.
  3. Delete all screens from a project → confirm the project is automatically removed.
  4. Delete a non-active screen → confirm activeScreen and activeProject remain unchanged and correct.
  5. Repeat with a single-screen project to confirm deletion cascades cleanly to project removal.

Further Comments

This fix is confined to the reducer and does not affect component rendering logic or the public API surface of the Playground. The reordering of operations is the minimal-footprint correction required — no architectural changes were needed.

If the project in question has downstream persistence (e.g., local storage or session sync), reviewers should verify that the deletion cascade to empty projects does not introduce any unintended side effects in those layers.

Summary by CodeRabbit

  • Bug Fixes
    • Deleting a screen now also removes its associated flow nodes and edges to prevent leftover data.
    • Removing the last screen no longer implicitly deletes the project context; project remains intact while screen-specific data is cleaned up.
    • App still selects an available remaining screen after a deletion when applicable.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Mar 2, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Mar 2, 2026

⚠️ No Changeset found

Latest commit: 4242fee

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

Walkthrough

DeleteScreen reducer was changed to remove associated flowNodes when a screen is deleted and to limit cleanup to screen-level removals; previous logic that implicitly deleted an active project when its last screen was removed was removed. No exported signatures changed.

Changes

Cohort / File(s) Summary
Screen deletion & flow cleanup
apps/uikit-playground/src/Context/reducer.ts
When deleting a screen, the reducer now also removes corresponding flowNodes after updating flowEdges. Logic that implicitly deleted the project when its last screen was removed was removed; cleanup is focused on screen-level deletions. No public API/export changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

type: bug

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: correcting project and screen state updates after deletion, which is the core change in the changeset.
Linked Issues check ✅ Passed The PR addresses all key objectives from issue #39240: reordering deletion logic, updating activeScreen/activeProject correctly, removing empty projects, and preventing invalid state references.
Out of Scope Changes check ✅ Passed All changes are scoped to the reducer logic in DeleteScreen as intended; no external dependencies, public API changes, or unrelated modifications are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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


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.

@CLAassistant
Copy link

CLAassistant commented Mar 2, 2026

CLA assistant check
All committers have signed the CLA.

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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/uikit-playground/src/Context/reducer.ts`:
- Around line 216-222: The reducer currently resets state.activeScreen to the
first screen whenever a screen is removed; change it so you only reassign
activeScreen if the deleted screen was the active one or if the activeScreen no
longer exists. In the reducer (around state.projects[activeProject].screens
handling), detect which screen is being deleted and if that id !==
state.activeScreen, leave state.activeScreen unchanged; if it was the active
screen (or the project becomes empty), then set state.activeScreen to the first
remaining screen or '' and handle deleting the empty project and resetting
state.activeProject as already implemented.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c30636 and 3a0a7b4.

📒 Files selected for processing (1)
  • apps/uikit-playground/src/Context/reducer.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/uikit-playground/src/Context/reducer.ts
🧠 Learnings (2)
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • apps/uikit-playground/src/Context/reducer.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • apps/uikit-playground/src/Context/reducer.ts

Comment on lines 216 to 222
if (state.projects[activeProject].screens.length > 0) {
state.activeScreen = state.projects[activeProject].screens[0];
} else {
delete state.projects[activeProject];
state.activeProject = '';
state.activeScreen = '';
}
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

Preserve activeScreen when deleting a non-active screen.

At Line 216, the reducer always reassigns activeScreen to the first remaining screen. That also runs when deleting a non-active screen, causing unexpected selection jumps.

Suggested fix
-      if (state.projects[activeProject].screens.length > 0) {
-        state.activeScreen = state.projects[activeProject].screens[0];
+      const remainingScreens = state.projects[activeProject].screens;
+      if (remainingScreens.length > 0) {
+        if (
+          state.activeScreen === action.payload ||
+          !remainingScreens.includes(state.activeScreen)
+        ) {
+          state.activeScreen = remainingScreens[0];
+        }
       } else {
         delete state.projects[activeProject];
         state.activeProject = '';
         state.activeScreen = '';
       }
📝 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
if (state.projects[activeProject].screens.length > 0) {
state.activeScreen = state.projects[activeProject].screens[0];
} else {
delete state.projects[activeProject];
state.activeProject = '';
state.activeScreen = '';
}
const remainingScreens = state.projects[activeProject].screens;
if (remainingScreens.length > 0) {
if (
state.activeScreen === action.payload ||
!remainingScreens.includes(state.activeScreen)
) {
state.activeScreen = remainingScreens[0];
}
} else {
delete state.projects[activeProject];
state.activeProject = '';
state.activeScreen = '';
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/uikit-playground/src/Context/reducer.ts` around lines 216 - 222, The
reducer currently resets state.activeScreen to the first screen whenever a
screen is removed; change it so you only reassign activeScreen if the deleted
screen was the active one or if the activeScreen no longer exists. In the
reducer (around state.projects[activeProject].screens handling), detect which
screen is being deleted and if that id !== state.activeScreen, leave
state.activeScreen unchanged; if it was the active screen (or the project
becomes empty), then set state.activeScreen to the first remaining screen or ''
and handle deleting the empty project and resetting state.activeProject as
already implemented.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/uikit-playground/src/Context/reducer.ts">

<violation number="1" location="apps/uikit-playground/src/Context/reducer.ts:217">
P1: `activeScreen` is unconditionally reset to the first remaining screen on every deletion, even when the deleted screen is not the active one. This causes the user's current selection to jump unexpectedly. The reassignment should only happen when the deleted screen was active or when `activeScreen` is no longer in the remaining screens list.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

].flowNodes.filter((node) => node.id !== action.payload);

if (state.projects[activeProject].screens.length > 0) {
state.activeScreen = state.projects[activeProject].screens[0];
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 2, 2026

Choose a reason for hiding this comment

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

P1: activeScreen is unconditionally reset to the first remaining screen on every deletion, even when the deleted screen is not the active one. This causes the user's current selection to jump unexpectedly. The reassignment should only happen when the deleted screen was active or when activeScreen is no longer in the remaining screens list.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/uikit-playground/src/Context/reducer.ts, line 217:

<comment>`activeScreen` is unconditionally reset to the first remaining screen on every deletion, even when the deleted screen is not the active one. This causes the user's current selection to jump unexpectedly. The reassignment should only happen when the deleted screen was active or when `activeScreen` is no longer in the remaining screens list.</comment>

<file context>
@@ -223,6 +213,14 @@ const reducer = (state: initialStateType, action: IAction) => {
       ].flowNodes.filter((node) => node.id !== action.payload);
 
+      if (state.projects[activeProject].screens.length > 0) {
+        state.activeScreen = state.projects[activeProject].screens[0];
+      } else {
+        delete state.projects[activeProject];
</file context>
Fix with Cubic

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: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/uikit-playground/src/Context/reducer.ts (1)

305-310: ⚠️ Potential issue | 🔴 Critical

Avoid dereferencing a project after deleting it in DeleteScreen.

If the deleted screen is the last one, Line 307 deletes state.projects[activeProject], but Line 313 and Line 318 still read from that deleted project. This can throw at runtime.

Suggested fix (reorder cleanup, then branch on emptiness)
 	case ActionTypes.DeleteScreen: {
-		delete state.screens[action.payload];
-		state.projects[activeProject].screens = [...state.projects[activeProject].screens.filter((id) => id !== action.payload)];
-		if (state.projects[activeProject].screens.length > 0) {
-			state.activeScreen = state.projects[activeProject].screens[0];
-		} else if (state.projects[activeProject].screens.length === 0) {
-			if (Object.keys(state.projects).length > 0) {
-				delete state.projects[activeProject];
-				state.activeProject = '';
-				state.activeScreen = '';
-			}
-		}
-
-		state.projects[activeProject].flowEdges = state.projects[activeProject].flowEdges.filter(
-			(edge) => edge.source !== action.payload && edge.target !== action.payload,
-		);
-		state.projects[activeProject].flowNodes = state.projects[activeProject].flowNodes.filter((node) => node.id !== action.payload);
+		const project = state.projects[activeProject];
+		delete state.screens[action.payload];
+		project.screens = project.screens.filter((id) => id !== action.payload);
+		project.flowEdges = project.flowEdges.filter((edge) => edge.source !== action.payload && edge.target !== action.payload);
+		project.flowNodes = project.flowNodes.filter((node) => node.id !== action.payload);
+
+		if (project.screens.length === 0) {
+			delete state.projects[activeProject];
+			const fallbackProjectId = Object.keys(state.projects)[0] ?? '';
+			state.activeProject = fallbackProjectId;
+			state.activeScreen = fallbackProjectId ? state.projects[fallbackProjectId].screens[0] ?? '' : '';
+			return { ...state };
+		}

Also applies to: 313-318

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/uikit-playground/src/Context/reducer.ts` around lines 305 - 310, In
DeleteScreen (reducer) avoid reading state.projects[activeProject] after you
delete it: first check whether the project will become empty by inspecting
state.projects[activeProject].screens length, then perform any reassignment of
state.activeProject/state.activeScreen and only afterwards delete
state.projects[activeProject]; or alternatively capture a const project =
state.projects[activeProject] and use project to read screens before deleting.
Update all branches that reference state.projects[activeProject] (the
DeleteScreen logic that adjusts activeProject/activeScreen) so they either read
from the cached project or reorder operations to set activeProject/activeScreen
before calling delete on state.projects.
♻️ Duplicate comments (1)
apps/uikit-playground/src/Context/reducer.ts (1)

303-305: ⚠️ Potential issue | 🟠 Major

Preserve activeScreen when deleting a non-active screen.

Line 304 always resets activeScreen to the first remaining screen, which causes unexpected selection jumps for non-active deletions.

Suggested fix
-      if (state.projects[activeProject].screens.length > 0) {
-        state.activeScreen = state.projects[activeProject].screens[0];
+      const remainingScreens = state.projects[activeProject].screens;
+      if (remainingScreens.length > 0) {
+        if (
+          state.activeScreen === action.payload ||
+          !remainingScreens.includes(state.activeScreen)
+        ) {
+          state.activeScreen = remainingScreens[0];
+        }
       } else if (state.projects[activeProject].screens.length === 0) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/uikit-playground/src/Context/reducer.ts` around lines 303 - 305, The
reducer currently always sets state.activeScreen =
state.projects[activeProject].screens[0] after a deletion, which resets
selection for non-active deletions; change the logic in the delete-screen branch
so you only reassign state.activeScreen when the removed screen was the active
one or the current activeScreen is no longer present (e.g., check if
deletedScreenId === state.activeScreen or
!state.projects[activeProject].screens.includes(state.activeScreen)), otherwise
preserve state.activeScreen; use the existing symbols state.activeScreen,
state.projects[activeProject].screens and activeProject to locate and implement
this conditional.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/uikit-playground/src/Context/reducer.ts`:
- Line 212: Remove the unresolved merge markers ("=======") left in
apps/uikit-playground/src/Context/reducer.ts (they appear around the reducer
implementation) — open the reducer file, delete the conflict marker lines at the
locations shown (around line 212 and 321), and reconcile any surrounding
duplicate/conflicting blocks so the reducer function (the reducer export/handle
switch) contains a single valid TypeScript implementation and the file compiles.

---

Outside diff comments:
In `@apps/uikit-playground/src/Context/reducer.ts`:
- Around line 305-310: In DeleteScreen (reducer) avoid reading
state.projects[activeProject] after you delete it: first check whether the
project will become empty by inspecting state.projects[activeProject].screens
length, then perform any reassignment of state.activeProject/state.activeScreen
and only afterwards delete state.projects[activeProject]; or alternatively
capture a const project = state.projects[activeProject] and use project to read
screens before deleting. Update all branches that reference
state.projects[activeProject] (the DeleteScreen logic that adjusts
activeProject/activeScreen) so they either read from the cached project or
reorder operations to set activeProject/activeScreen before calling delete on
state.projects.

---

Duplicate comments:
In `@apps/uikit-playground/src/Context/reducer.ts`:
- Around line 303-305: The reducer currently always sets state.activeScreen =
state.projects[activeProject].screens[0] after a deletion, which resets
selection for non-active deletions; change the logic in the delete-screen branch
so you only reassign state.activeScreen when the removed screen was the active
one or the current activeScreen is no longer present (e.g., check if
deletedScreenId === state.activeScreen or
!state.projects[activeProject].screens.includes(state.activeScreen)), otherwise
preserve state.activeScreen; use the existing symbols state.activeScreen,
state.projects[activeProject].screens and activeProject to locate and implement
this conditional.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a0a7b4 and 4242fee.

📒 Files selected for processing (1)
  • apps/uikit-playground/src/Context/reducer.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/uikit-playground/src/Context/reducer.ts
🧠 Learnings (2)
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • apps/uikit-playground/src/Context/reducer.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • apps/uikit-playground/src/Context/reducer.ts
🧬 Code graph analysis (1)
apps/uikit-playground/src/Context/reducer.ts (2)
apps/uikit-playground/src/utils/filterEdges.ts (1)
  • filterEdges (3-5)
packages/apps-engine/tests/test-data/utilities.ts (1)
  • getDate (193-195)
🪛 Biome (2.4.4)
apps/uikit-playground/src/Context/reducer.ts

[error] 212-212: Expected a statement but instead found '======='.

(parse)


[error] 321-321: Expected a statement but instead found '======='.

(parse)

(edge) =>
edge.source !== action.payload && edge.target !== action.payload
);
=======
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

Remove unresolved merge-conflict markers before merge.

======= at Line 212 and Line 321 makes the reducer invalid TypeScript and blocks build/runtime parsing.

Suggested cleanup
-=======
...
-=======

Also applies to: 321-321

🧰 Tools
🪛 Biome (2.4.4)

[error] 212-212: Expected a statement but instead found '======='.

(parse)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/uikit-playground/src/Context/reducer.ts` at line 212, Remove the
unresolved merge markers ("=======") left in
apps/uikit-playground/src/Context/reducer.ts (they appear around the reducer
implementation) — open the reducer file, delete the conflict marker lines at the
locations shown (around line 212 and 321), and reconcile any surrounding
duplicate/conflicting blocks so the reducer function (the reducer export/handle
switch) contains a single valid TypeScript implementation and the file compiles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(uikit-playground): activeScreen and activeProject not updated correctly after deletion

2 participants